Tomáš Müller (
https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=5b50823...
) *created* an issue
Hibernate ORM (
https://hibernate.atlassian.net/browse/HHH?atlOrigin=eyJpIjoiYWE5ZGY3MDU5...
) / Bug (
https://hibernate.atlassian.net/browse/HHH-16825?atlOrigin=eyJpIjoiYWE5ZG...
) HHH-16825 (
https://hibernate.atlassian.net/browse/HHH-16825?atlOrigin=eyJpIjoiYWE5ZG...
) Cascading an entity with a composite key causes NullPointerException in
AbstractClassJavaType.extractHashCode (
https://hibernate.atlassian.net/browse/HHH-16825?atlOrigin=eyJpIjoiYWE5ZG...
)
Issue Type: Bug Affects Versions: 6.2.5 Assignee: Unassigned Attachments: Bottom.java,
HashTest.java, Middle.java, Top.java Components: hibernate-core Created: 20/Jun/2023 06:14
AM Environment: Java 11 using MySQL 8 or Oracle 18 Priority: Major Reporter: Tomáš Müller
(
https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=5b50823...
)
I am in the process of migrating an application from Hibernate 4.3 to Hibernate 6.2. I
have noticed something that works differently, causing a NullPointerException when there
are two levels of one-to-many nesting with the bottom entity containing a composite id.
That is when the composite id contains a relation that is being persisted together with
the entity it refers to.
Here is our example (simplified). Imagine having a Top entity that has a one-to-many
relation to a Middle entity that has a one-to-many relation to a Bottom entity with a
composite key (including id of the middle entity):
Top entity:
@Entity
@Table(name = "top")
public class Top {
private UUID id;
private String name;
private Set<Middle> middles;
@Id
@GeneratedValue
@Column(name = "id")
public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; }
@Column(name = "name")
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@OneToMany(mappedBy = "top", cascade = { CascadeType.ALL })
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public Set<Middle> getMiddles() { return middles; }
public void setMiddles(Set<Middle> middles) { this.middles = middles; }
public void addMiddle(Middle middle) {
if (middles == null) middles = new HashSet<Middle>();
middles.add(middle);
}
}
Middle entity:
@Entity
@Table(name = "middle")
public class Middle {
private UUID id;
private Top top;
private Set<Bottom> bottoms;
@Id
@GeneratedValue
@Column(name = "id")
public UUID getId() { return id; }
public void setId(UUID id) { this.id = id; }
@ManyToOne(optional = false)
@JoinColumn(name = "top_id", nullable = false)
public Top getTop() { return top; }
public void setTop(Top student) { this.top = student; }
@OneToMany(mappedBy = "middle", cascade = {CascadeType.ALL})
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public Set<Bottom> getBottoms() { return bottoms; }
public void setBottoms(Set<Bottom> bottoms) { this.bottoms = bottoms; }
public void addBottom(Bottom bottom) {
if (bottoms == null) bottoms = new HashSet<Bottom>();
bottoms.add(bottom);
}
}
Bottom entity:
@Entity
@Table(name = "bottom")
public class Bottom {
private Middle middle;
private Integer type;
private String note;
@Id
@ManyToOne(optional = false)
@JoinColumn(name = "middle_id", nullable = false)
public Middle getMiddle() { return middle; }
public void setMiddle(Middle middle) { this.middle = middle; }
@Id
@Column(name = "type")
public Integer getType() { return type; }
public void setType(Integer type) { this.type = type; }
@Column(name = "note", nullable = true, length = 2048)
public String getNote() { return note; }
public void setNote(String note) { this.note = note; }
}
Now, imagine that we want to update the top entity by adding a middle entity with a bottom
entity to it. Like this:
final Session hibSession1 = sf.openSession();
Transaction t1 = hibSession1.beginTransaction();
// Lookup a top entity
Top top = hibSession1.createQuery("from Top where name = :name",
Top.class).setParameter("name", "Top 1").uniqueResult();
System.out.println("Top: " + top.getId() + ", " + top.getName());
// Add one middle entity with a single bottom entity to the top entity
Middle m1 = new Middle();
m1.setTop(top); top.addMiddle(m1);
Bottom b1 = new Bottom();
b1.setMiddle(m1); m1.addBottom(b1); b1.setType(0); b1.setNote("Bottom 1");
// update the top entity
hibSession1.merge(top);
t1.commit();
hibSession1.close();
However, this fails with the following exception:
java.lang.NullPointerException
at
org.hibernate.type.descriptor.java.AbstractClassJavaType.extractHashCode(AbstractClassJavaType.java:93)
at
org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:216)
at
org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:225)
at org.hibernate.type.EntityType.getHashCode(EntityType.java:362)
at org.hibernate.type.ComponentType.getHashCode(ComponentType.java:270)
at org.hibernate.engine.spi.EntityKey.generateHashCode(EntityKey.java:60)
at org.hibernate.engine.spi.EntityKey.<init>(EntityKey.java:53)
at
org.hibernate.internal.AbstractSharedSessionContract.generateEntityKey(AbstractSharedSessionContract.java:618)
at
org.hibernate.event.internal.DefaultMergeEventListener.entityState(DefaultMergeEventListener.java:189)
at
org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:147)
at
org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:142)
at
org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:126)
at
org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:869)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:840)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:253)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:243)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:513)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:434)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:547)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:477)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:437)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:153)
at
org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:490)
at
org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:239)
at
org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:152)
at
org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:142)
at
org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:126)
at
org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:869)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:840)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:253)
at org.hibernate.engine.spi.CascadingActions$6.cascade(CascadingActions.java:243)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:513)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:434)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:547)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:477)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:437)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:220)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:153)
at
org.hibernate.event.internal.DefaultMergeEventListener.cascadeOnMerge(DefaultMergeEventListener.java:570)
at
org.hibernate.event.internal.DefaultMergeEventListener.entityIsPersistent(DefaultMergeEventListener.java:212)
at
org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:155)
at
org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:142)
at
org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:126)
at
org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:80)
at
org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:848)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:834)
at org.hibernate.bugs.hash.HashTest.testMerge(HashTest.java:52)
Everything works fine when
* the top entity is being created (with the middle and bottom entities) instead (calling
persist instead of merge)
* when the middle entities are persisted before (or instead of) merging the top entity
* when the bottom entity has a generated id instead of a reference to the middle entity
See the attached files for the whole test.
(
https://hibernate.atlassian.net/browse/HHH-16825#add-comment?atlOrigin=ey...
) Add Comment (
https://hibernate.atlassian.net/browse/HHH-16825#add-comment?atlOrigin=ey...
)
Get Jira notifications on your phone! Download the Jira Cloud app for Android (
https://play.google.com/store/apps/details?id=com.atlassian.android.jira....
) or iOS (
https://itunes.apple.com/app/apple-store/id1006972087?pt=696495&ct=Em...
) This message was sent by Atlassian Jira (v1001.0.0-SNAPSHOT#100227- sha1:8ffa416 )