Skip to content

Commit f9d2e8e

Browse files
committed
HHH-18883 fix for TransientObjectException
1 parent 1fe23ae commit f9d2e8e

File tree

3 files changed

+200
-7
lines changed

3 files changed

+200
-7
lines changed

hibernate-core/src/main/java/org/hibernate/engine/internal/ForeignKeys.java

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -334,10 +334,11 @@ public static boolean isTransient(
334334
* Return the identifier of the persistent or transient object, or throw
335335
* an exception if the instance is "unsaved"
336336
* <p>
337-
* Used by OneToOneType and ManyToOneType to determine what id value should
337+
* Used by {@link org.hibernate.type.OneToOneType} and
338+
* {@link org.hibernate.type.ManyToOneType} to determine what id value should
338339
* be used for an object that may or may not be associated with the session.
339340
* This does a "best guess" using any/all info available to use (not just the
340-
* EntityEntry).
341+
* {@link EntityEntry}).
341342
*
342343
* @param entityName The name of the entity
343344
* @param object The entity instance
@@ -357,9 +358,9 @@ public static Object getEntityIdentifierIfNotUnsaved(
357358
else {
358359
final Object id = session.getContextEntityIdentifier( object );
359360
if ( id == null ) {
360-
// context-entity-identifier returns null explicitly if the entity
361-
// is not associated with the persistence context; so make some
362-
// deeper checks...
361+
// context-entity-identifier always returns null if the
362+
// entity is not associated with the persistence context;
363+
// so make some deeper checks...
363364
throwIfTransient( entityName, object, session );
364365
return session.getEntityPersister( entityName, object ).getIdentifier( object, session );
365366
}
@@ -369,6 +370,21 @@ public static Object getEntityIdentifierIfNotUnsaved(
369370
}
370371
}
371372

373+
public static Object getEntityIdentifier(
374+
final String entityName,
375+
final Object object,
376+
final SharedSessionContractImplementor session) {
377+
if ( object == null ) {
378+
return null;
379+
}
380+
else {
381+
final Object id = session.getContextEntityIdentifier( object );
382+
return id == null
383+
? session.getEntityPersister( entityName, object ).getIdentifier( object, session )
384+
: id;
385+
}
386+
}
387+
372388
private static void throwIfTransient(String entityName, Object object, SharedSessionContractImplementor session) {
373389
if ( isTransient( entityName, object, Boolean.FALSE, session ) ) {
374390
throw new TransientObjectException( "Entity references an unsaved transient instance of '"

hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/internal/EntityInitializerImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,14 +1583,14 @@ protected void registerPossibleUniqueKeyEntries(
15831583
final Type type = entry.getPropertyType();
15841584

15851585
// polymorphism not really handled completely correctly,
1586-
// perhaps...well, actually its ok, assuming that the
1586+
// perhaps...well, actually it's ok, assuming that the
15871587
// entity name used in the lookup is the same as the
15881588
// one used here, which it will be
15891589

15901590
if ( resolvedEntityState[index] != null ) {
15911591
final Object key;
15921592
if ( type instanceof ManyToOneType manyToOneType ) {
1593-
key = ForeignKeys.getEntityIdentifierIfNotUnsaved(
1593+
key = ForeignKeys.getEntityIdentifier(
15941594
manyToOneType.getAssociatedEntityName(),
15951595
resolvedEntityState[index],
15961596
session
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.bytecode.enhancement.batch;
6+
7+
import jakarta.persistence.*;
8+
import org.hibernate.Hibernate;
9+
import org.hibernate.cfg.AvailableSettings;
10+
import org.hibernate.testing.orm.junit.*;
11+
import org.junit.jupiter.api.Test;
12+
13+
import java.util.Objects;
14+
import java.util.UUID;
15+
16+
import static jakarta.persistence.FetchType.LAZY;
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
@DomainModel(
20+
annotatedClasses = {
21+
HandleVersionNumbersInitializedToNegativeValueTests.RootEntity.class,
22+
HandleVersionNumbersInitializedToNegativeValueTests.ChildEntity.class
23+
}
24+
)
25+
@ServiceRegistry(
26+
settings = {
27+
// For your own convenience to see generated queries:
28+
@Setting(name = AvailableSettings.SHOW_SQL, value = "true"),
29+
@Setting(name = AvailableSettings.FORMAT_SQL, value = "true"),
30+
}
31+
)
32+
@SessionFactory
33+
class HandleVersionNumbersInitializedToNegativeValueTests {
34+
35+
@Test @JiraKey("HHH-18883")
36+
void hhh18883Test(SessionFactoryScope scope) {
37+
var id = UUID.randomUUID();
38+
scope.inTransaction(session -> {
39+
RootEntity rootEntity = new RootEntity(id, new ChildEntity());
40+
session.persist(rootEntity);
41+
});
42+
43+
scope.inTransaction(session -> {
44+
RootEntity rootEntity = session.find(RootEntity.class, id);
45+
assertThat(rootEntity).isNotNull();
46+
assertThat(rootEntity.getChildEntity()).isNotNull();
47+
});
48+
}
49+
50+
51+
@Entity
52+
@Table
53+
public static class RootEntity {
54+
55+
@Id
56+
private UUID id;
57+
58+
@OneToOne(mappedBy = "rootEntity", cascade = CascadeType.ALL)
59+
@PrimaryKeyJoinColumn
60+
private ChildEntity childEntity;
61+
62+
@Version
63+
private int version = -1;
64+
65+
public RootEntity() {
66+
}
67+
68+
public RootEntity(UUID id, ChildEntity childEntity) {
69+
setId(id);
70+
setChildEntity(childEntity);
71+
}
72+
73+
public UUID getId() {
74+
return id;
75+
}
76+
77+
public void setId(UUID id) {
78+
this.id = id;
79+
}
80+
81+
public ChildEntity getChildEntity() {
82+
return childEntity;
83+
}
84+
85+
public void setChildEntity(ChildEntity childEntity) {
86+
this.childEntity = childEntity;
87+
if (childEntity != null) {
88+
childEntity.setRootEntity(this);
89+
}
90+
}
91+
92+
public int getVersion() {
93+
return version;
94+
}
95+
96+
public void setVersion(int version) {
97+
this.version = version;
98+
}
99+
100+
@Override
101+
public boolean equals(Object o) {
102+
if (this == o) {
103+
return true;
104+
}
105+
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
106+
return false;
107+
}
108+
RootEntity event = (RootEntity) o;
109+
return Objects.equals(id, event.id);
110+
}
111+
112+
@Override
113+
public int hashCode() {
114+
return 0;
115+
}
116+
}
117+
118+
@Entity
119+
@Table
120+
public static class ChildEntity {
121+
122+
@Id
123+
private UUID id;
124+
125+
@OneToOne(fetch = LAZY)
126+
@MapsId
127+
private RootEntity rootEntity;
128+
129+
@Version
130+
private int version = -1;
131+
132+
public ChildEntity() {
133+
}
134+
135+
public UUID getId() {
136+
return id;
137+
}
138+
139+
public void setId(UUID id) {
140+
this.id = id;
141+
}
142+
143+
public RootEntity getRootEntity() {
144+
return rootEntity;
145+
}
146+
147+
public void setRootEntity(RootEntity rootEntity) {
148+
this.rootEntity = rootEntity;
149+
}
150+
151+
public int getVersion() {
152+
return version;
153+
}
154+
155+
public void setVersion(int version) {
156+
this.version = version;
157+
}
158+
159+
@Override
160+
public boolean equals(Object o) {
161+
if (this == o) {
162+
return true;
163+
}
164+
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
165+
return false;
166+
}
167+
ChildEntity event = (ChildEntity) o;
168+
return Objects.equals(id, event.id);
169+
}
170+
171+
@Override
172+
public int hashCode() {
173+
return 0;
174+
}
175+
}
176+
177+
}

0 commit comments

Comments
 (0)