I performed a batch operation on seemingly simple and similar data, but for some entries Hibernate used proxies whereas for others it didn’t, even though the data is very similar. I narrowed the problem down to a minimum to reproduce it.
I know how to work around this problem, but I would like to know if this is expected behavior or a bug.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Comment {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
@ManyToOne(fetch = FetchType.LAZY)
private Comment replyTo;
public Long getId() { return id; }
public Comment() { }
public Comment(String firstName) {
this.firstName = firstName;
}
public Comment(String firstName, Comment replyTo) {
this(firstName);
this.replyTo = replyTo;
}
}
@Entity
public class ProductComment extends Comment {
public ProductComment() { }
public ProductComment(String firstName) {
super(firstName);
}
public ProductComment(String firstName, ProductComment replyTo) {
super(firstName, replyTo);
}
@Override
public String toString() {
return "ProductComment: " + getId();
}
}
public class App {
private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa-template");
public static void main(String[] args) {
// Database initialization code
runInTx(em -> {
ProductComment c1 = new ProductComment("person1");
ProductComment c2 = new ProductComment("person2", c1);
ProductComment c3 = new ProductComment("person1", c2);
em.persist(c1);
em.persist(c2);
em.persist(c3);
});
runInTx(em -> {
// Query a list of some specific comments
List<Comment> comments = em.createQuery("select c from Comment c where c.firstName = 'person1'").getResultList();
// Loop through those comments and print all replies
for (Comment comment : comments) {
System.out.println("-comment:" + comment);
List<Comment> replies = em.createQuery("select c from Comment c where replyTo = ?1")
.setParameter(1, comment)
.getResultList();
for (Comment reply : replies) {
System.out.println("\t-reply: " + reply.getId());
// This fails when a proxy is used
if (! (reply instanceof ProductComment)) {
throw new IllegalStateException("instanceof failed");
}
}
}
});
}
private static void runInTx(Consumer<EntityManager> unitOfWork) {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
unitOfWork.accept(em);
em.getTransaction().commit();
em.close();
}
}
Output:
Hibernate: insert into Comment (firstName, replyTo_id, DTYPE) values (?, ?, 'ProductComment')
Hibernate: insert into Comment (firstName, replyTo_id, DTYPE) values (?, ?, 'ProductComment')
Hibernate: insert into Comment (firstName, replyTo_id, DTYPE) values (?, ?, 'ProductComment')
Hibernate: select comment0_.id as id2_0_, comment0_.firstName as firstnam3_0_, comment0_.replyTo_id as replyto_4_0_, comment0_.DTYPE as dtype1_0_ from Comment comment0_ where comment0_.firstName='person1'
-comment:ProductComment: 1
Hibernate: select comment0_.id as id2_0_, comment0_.firstName as firstnam3_0_, comment0_.replyTo_id as replyto_4_0_, comment0_.DTYPE as dtype1_0_ from Comment comment0_ where comment0_.replyTo_id=?
-reply: 2
Exception in thread "main" java.lang.IllegalStateException: instanceof failed
at org.example.App.lambda$main$1(App.java:53)
at org.example.App.runInTx(App.java:64)
at org.example.App.main(App.java:37)
This problem doesn’t happen when the following database initialization code is used instead:
ProductComment c1 = new ProductComment("person1");
ProductComment c2 = new ProductComment("person2", c1);
ProductComment c3 = new ProductComment("person2", c1);
ProductComment c4 = new ProductComment("person3", c1);
em.persist(c1);
em.persist(c2);
em.persist(c3);
em.persist(c4);
So it looks like something like this happens when the first database initialization code is used:
c1 and c3 are fully loaded in the persistence context, using the first query. C3 has a replyTo
to c2, which would be lazily loaded with a proxy if accessed. When all replies to ‘person1’ are queried (using the second query), c2 would be returned in that query. Normally Hibernate would fully load C2, but because where exists a different object (c3) that is referencing c2, c2 somehow isn’t fully loaded anymore. Where IMO the expected behavior would that that c2 would be fully loaded without proxy, since all data of c2 is queried.
Is this a bug?