Search code examples
hibernatejpajpa-2.0hibernate-entitymanager

Removing both @Entity and @MappedSuperclass in the same class


One of the existing classes (class A) has both @Entity and @MappedSuperclass annotations and the child class (class B extends A) has @Entity.

Although this in not right, it is working fine with hibernate-entitymanager 3.3. Both entities have their own table and the table for class B has the same columns as class A plus one additional column that corresponds to its own property.

I'm trying to fix this mistake that was sitting there for years so I can migrate to 3.6 as it does not allow both annotations. (it throws org.hibernate.AnnotationException: An entity cannot be annotated with both @Entity and @MappedSuperclass:)

I tried to replace @MappedSuperclass with @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) but now I'm getting runtime errors when loading another class that one of their properties is a set of B.

org.hibernate.util.JDBCExceptionReporter.logExceptions(JDBCExceptionReporter.java:78) - Unknown column 'A.createdDateTime' in 'order clause'
org.hibernate.exception.SQLGrammarException: could not initialize a collection: [com.acmy.AnotherClassC.classBset#975905b5-d59c-4e53-98dd-30cf39b0c831]

What would be a smooth way of fixing class A that uses both @Entity and @MappedSuperclass to use @Entity and @Inheritance? Do I need any SQL to fix existing data?


Solution

  • You can remove the @MappedSuperClass Annotation - but leave the @Entity and the @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS).

    You can not use @GeneratedValue(strategy = GenerationType.AUTO) but i.e. @GeneratedValue(strategy = GenerationType.TABLE)

    UPDATE

    An example - works for me

    Register the generator in a package-info Class

    package-info.java

    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    package db;
    
    import org.hibernate.annotations.GenericGenerator;
    

    Your class A ClassA.java

    package db;
    
    import java.io.Serializable;
    import java.util.Objects;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.Inheritance;
    import javax.persistence.InheritanceType;
    
    @Entity
    @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
    public class ClassA implements Serializable {
        private static final long serialVersionUID = 1L;
    
        @Id
        @GeneratedValue(generator = "system-uuid")
        private String id;
    
        private String value;
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getValue() {
            return value;
        }
    
        public void setValue(String value) {
            this.value = value;
        }
    
        @Override
        public int hashCode() {
            int hash = 7;
            hash = 67 * hash + Objects.hashCode(this.id);
            return hash;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final ClassA other = (ClassA) obj;
            if (!Objects.equals(this.id, other.id)) {
                return false;
            }
            return true;
        }
    
        @Override
        public String toString() {
            return "db.ClassA[ id=" + id.toString() + " ]";
        }
    }
    

    And the inherited Class B ClassB.java

    package db;
    
    import javax.persistence.DiscriminatorValue;
    import javax.persistence.Entity;
    
    @Entity
    @DiscriminatorValue("B")
    public class ClassB extends ClassA {
        private static final long serialVersionUID = 1L;
    
        @Override
        public String toString() {
            return "db.ClassB[ id=" + getId() + " ]";
        }
    }
    

    Last but not least a main class for testing

    TestDb.java

    package db;
    
    import java.util.List;
    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;
    
    public class TestDb {
        EntityManager em;
    
        public static void main(String[] args) {
            new TestDb().start();
        }
    
        public void start() {
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("dbtest");
            em = emf.createEntityManager();
            em.getTransaction().begin();
            ClassA a = new ClassA();
            a.setValue("A");
            em.persist(a);
            a = new ClassA();
            a.setValue("B");
            em.persist(a);
            ClassB b = new ClassB();
            b.setValue("C");
            em.persist(b);
            em.getTransaction().commit();
    
            em.getTransaction().begin();
            List list = em.createQuery("from " + ClassA.class.getSimpleName() + " c order by c.value").getResultList();
            System.out.println("List " + list);
            em.getTransaction().commit();
        }
    }
    

    The persistence.xml looks like this. I use the h2database with an im-memory database

    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
        <persistence-unit name="dbtest" transaction-type="RESOURCE_LOCAL">
            <provider>org.hibernate.ejb.HibernatePersistence</provider>
            <class>db.ClassA</class>
            <class>db.ClassB</class>
            <properties>
                <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:"/>
                <property name="javax.persistence.jdbc.user" value="app"/>
                <!--property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/-->
                <property name="javax.persistence.jdbc.password" value="app"/>
                <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
                <property name="javax.persistence.schema-generation.database.action" value="create"/>
                <property name="hibernate.hbm2ddl.auto" value="update"/>
            </properties>
        </persistence-unit>
    </persistence>