Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA Inheritance issue

Working with JPA 1 (hibernate-core version 3.3.0.SP1 and hibernate-entitymanager version 3.4.0.GA) : I've some entities similar to those defined below, where ChildOne and ChildTwo extends from the Father entity.

@Entity
@Table(name = "TABLE_FATHER")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(discriminatorType = DiscriminatorType.INTEGER, name = Father.C_ID_CTG)
public class Father {

@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "sq")
@Column(name = "ID_PK", nullable = false)
@BusinessId
private Long id;
...
} 

@Entity
@Table(name = "TABLE_CHILD_ONE")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorValue(Categories.ID_CTG_ONE)
public class ChildOne extends Father {
    ...
}

@Entity
@Table(name = "TABLE_CHILD_TWO")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorValue(Categories.ID_CTG_TWO)
public class ChildTwo extends Element {
    ...
} 

Let's say I've one entity having a Father element, and another having a collection of father elements. In both cases, should go the children entities.

@Entity
@Table(name = "TABLE_ONE")
public class OneTable {

@JoinColumn(name = "ID_PK", referencedColumnName = "ID_PK", nullable = false)
@ManyToOne(optional = false, fetch = FetchType.LAZY)
private Father element; 
    ...
} 

@Entity
@Table(name = "TABLE_ANOTHER")
public class Another  {

@Fetch(FetchMode.JOIN)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "id", fetch = FetchType.LAZY)
private Collection<Father> elementCollection;

    ...
} 

I'm expecting to obtain always the children elements but when I get the element getElement() returns the father element and, on the other hand, when I get the collection getElementCollection() the children elements are coming.

Apparently, the @JoinColumn is the cause of this behaviour, doing the join with the father table and forgetting the children elements. The collection is working as expected.

How could I get the children element with a getElement() call? Any ideas or workarround? Thanks in advance.

like image 513
elcadro Avatar asked Jul 03 '14 11:07

elcadro


People also ask

Does JPA support inheritance?

JPA supports the following inheritance models: - SINGLE_TABLE – One table per class hierarchy. In terms of performance and easy implementation, this is the best strategy. The downside is that all properties from the subclass must be nullable.

What is a JPA inheritance?

JPA Inheritence Overview Inheritence is a key feature of object-oriented programming language in which a child class can acquire the properties of its parent class. This feature enhances reusability of the code. The relational database doesn't support the mechanism of inheritance.

What inheritance strategies does JPA offer?

There are three inheritance strategies defined from the InheritanceType enum, SINGLE_TABLE , TABLE_PER_CLASS and JOINED . Single table inheritance is the default, and table per class is an optional feature of the JPA spec, so not all providers may support it.


1 Answers

The problem is not caused by @JoinColumn. The reason is Lazy Loading. I manage to pinpoint your problem in simpler example. Forgive me for changing convention from Father to Parent.

In the example below, uninitialized Element is type of jpa.inheritance.issue.Parent_$$_javassist_1. It is a Hibernate Proxy - dynamically created subclass of Parent. You can "unproxy" it by invoking Hibernate proprietary API getHibernateLazyInitializer().getImplementation().

Collection of elementCollection is also Lazy Initialized. The type of the collection is org.hibernate.collection.PersistentBag which is being initilized with correct data at the time of first access. Collection is initialized all at once. Please see the test which successfully passed green with your exact version of Hibernate (3.3.0.SP1/3.4.0.GA).

    @Test
    public void test() {
        Child c = new Child();
        em.persist(c);

        Another a = new Another();
        a.setElement(c);
        Collection<Parent> col = new ArrayList<Parent>();
        col.add(c);
        a.setElementCollection(col);
        em.persist(a);
        c.setAnother(a);

        long idx = a.getId();
        tx.commit();

        // I'm cleaning the cache to be sure that call to a.getElement() will return proxy.
        em.clear();
        tx = em.getTransaction();
        tx.begin();

        a = em.find(Another.class, idx);
        Assert.assertNotNull(a);
        Parent p = a.getElement();
        // At this point p is a type of jpa.inheritance.issue.Parent_$$_javassist_1

        Assert.assertTrue(p instanceof Parent);
        Assert.assertFalse(p instanceof Child);

        // At this point a.elements is a not initialized (empty) collection of type org.hibernate.collection.PersistentBag
        // When we access this collection for the first time, records are read from the database 
        Assert.assertEquals(1, a.getElementCollection().size());

        if (p instanceof HibernateProxy) {
            p =
                    (Parent) ((HibernateProxy) p).getHibernateLazyInitializer()
                            .getImplementation();
        }

        // At this point p is a type of jpa.inheritance.issue.Child
        Assert.assertTrue(p instanceof Child);
    }

    @Entity
    public class Another {

        @JoinColumn(name = "element_id", referencedColumnName = "id", nullable = false)
        @ManyToOne(fetch=FetchType.LAZY)
        private Parent element; 
        public Parent getElement() {
            return element;
        }

        public void setElement(Parent element) {
            this.element = element;
        }

        @OneToMany(cascade = CascadeType.ALL, mappedBy = "another", fetch = FetchType.LAZY)
        public Collection<Parent> elements;

        public Collection<Parent> getElementCollection() {
            return elements;
        }

        public void setElementCollection(Collection<Parent> elementCollection) {
            this.elements = elementCollection;
        }

        // @Id ...
    }

    @Entity
    @Inheritance(strategy = InheritanceType.JOINED)
    public class Parent {
        @ManyToOne
        private Another another;

        public Another getAnother() {
            return another;
        }

        public void setAnother(Another another) {
            this.another = another;
        }

        // @Id ...
    }

    @Entity
    public class Child extends Parent {         
    }

You don't need @DiscriminatorColumn nor @DiscriminatorValue because those annotations are needed with InheritanceType.SINGLE_TABLE as an only resort to determine the type. With InheritanceType.JOINED Hibernate is able to determine polymorphic type by checking if there is a record in both (Parent and Child) tables with the same Id. You can turn on hibernate logging to see how the query to determine the type looks like. It works like this:

select
    another0_.id as id0_1_,
    another0_.element_id as element2_0_1_,
    parent1_.id as id1_0_,
    parent1_1_.name as name2_0_,
    case
        when parent1_1_.id is not null then 1
        when parent1_.id is not null then 0
        else -1
    end as clazz_0_
from
    Another another0_
inner join
    Parent parent1_
        on another0_.element_id=parent1_.id
left outer join
    Child parent1_1_
        on parent1_.id=parent1_1_.id
where
    another0_.id=?
like image 102
zbig Avatar answered Oct 14 '22 21:10

zbig