Hibernate, creating a subtype of the wrong object in a relationship

I have a strange problem where hibernation does not create the expected entity type in many respects between relataionship. We have the following objects with a subclass hierarchy (simplified):

@Entity @Table(name = "A") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1) public abstract class A { @Id ... public Long getId() { ... } ... } @Entity @DiscriminatorValue("1") public class A1 extends A { ... } @Entity @DiscriminatorValue("2") public class A2 extends A { ... } @Entity @Table(name = "B") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1) public abstract class B<AClass extends A> { protected AClass a; @Id ... public Long getId() { ... } ... public abstract AClass getA(); public void setA(AClass a) { ... } } @Entity @DiscriminatorValue("1") public class B1 extends B<A1> { ... @Override @ManyToOne(fetch = EAGER) @JoinColumn(name = "A_ID") public A1 getA() { ... } } @Entity @DiscriminatorValue("2") public class B2 extends B<A2> { ... @Override @ManyToOne(fetch = EAGER) @JoinColumn(name = "A_ID") public A2 getA() { ... } } 

In persistence.xml both entities are declared in order

 A2 A1 B2 B1 

Now I create instances of A1 and B1 in the database:

 A1 a1 = new A1(); entityManager.persist(a1); B1 b1 = new B1(); b1.setA(a1); entityManager.persist(b1); 

I see that the instances are saved in the database correctly, each of them has the identifier 1, DISCRIMINATOR is also 1, A_ID in B is also 1.

When I'm trying to get B now (in another hibernation session):

 B b = entityManager.find(B.class, 1L); 

I get an exception:

 org.hibernate.PropertyAccessException: Exception occurred inside getter of B Caused by: java.lang.ClassCastException: A2 cannot be cast to A1 at B1.getA(B1.java:61) ... 108 more 

With debugging, I discovered that hibernate was creating the correct object of type B1 and creating the wrong object of type A2 to relate to A. The correct type A1 is created if the order in persistence.xml changed. It seems that hibernate does not account for the DISCRIMINATOR column of table A in this case, but always creates the first subtype declared in the configuration. How can this be fixed? Is there something wrong with annotations?

(I also had a specific implementation of the getA() method with its annotations in supertype B at first, but this leads to similar problems.)

+7
java hibernate jpa discriminator
source share
3 answers

With Hibernate 5.0.2.Final, I was able to do your work example using @ManyToOne(..., targetEntity = A.class) . I also replaced public abstract AClass getA(); regular getter.

 @Entity @Table(name = "B") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 1) public abstract class B<AClass extends A> { private Long id; private AClass a; @Id @GeneratedValue public Long getId() { return id; } public void setId(Long id) { this.id = id; } @ManyToOne(fetch = FetchType.EAGER, targetEntity = A.class) @JoinColumn(name = "A_ID") public AClass getA() { return a; } public void setA(AClass a) { this.a = a; } } 
 @Entity @DiscriminatorValue("1") public class B1 extends B<A1> { // no need to override getA() } 
 @Entity @DiscriminatorValue("2") public class B2 extends B<A2> { // no need to override getA() } 

I did not find anything like this behavior in the documentation. Therefore, I have only my observations:

  • Without targetEntity = A.class Hibernate didnโ€™t even query the DISCRIMINATOR column of table A , when he eagerly selected rows from A along with B , as if he had already decided on the actual type of A ,
  • When I added targetEntity = A.class , A.DISCRIMINATOR appeared in the queries, and the objects were created with the right subclasses of class A
+3
source share

You use the same join column ( A_ID ) in subclasses B1 and B2 .

Use different in each subclass:

 @Entity @DiscriminatorValue("1") public class B1 extends B<A1> { @Override @ManyToOne(fetch = EAGER) @JoinColumn(name = "A1_ID") public A1 getA() { ... } } @Entity @DiscriminatorValue("2") public class B2 extends B<A2> { @Override @ManyToOne(fetch = EAGER) @JoinColumn(name = "A2_ID") public A2 getA() { ... } } 

Although it might make sense to reuse a column (with different columns, each will be null for each record depending on the subclass), it seems that Hibernate uses internal column names to uniquely identify some display elements in the same table, so it probably ignores the definition a multi-valued mapping in B1 and also uses one of B2 for it (since B2 is defined before B1 in persistence.xml ).

+3
source share

Later, but just to add that Hibernate throws the same error (returns the wrong subtype) when you name subclass fields with the same names in classes that have a relationship with them, for example

 @Entity public abstract class Box { ... } @Entity public class LargeBox extends Box { ... } @Entity public class SmallBox extends Box { ... } @Entity public class A { @ManyToOne private LargeBox box; } @Entity public class B { @ManyToOne private SmallBox box; } 

The above message will throw an error while reading instances of class B from the database when the box is cast to a LargeBox. Update to:

 @Entity public class A { @ManyToOne private LargeBox largeBox; } @Entity public class B { @ManyToOne private SmallBox smallBox; } 

... fixed it for me.

0
source share

All Articles