Skip to content

Commit 3d2d449

Browse files
committed
HHH-18229 Handle null owner key for collections
1 parent 492fabd commit 3d2d449

File tree

13 files changed

+404
-71
lines changed

13 files changed

+404
-71
lines changed

hibernate-core/src/main/java/org/hibernate/action/internal/AbstractEntityInsertAction.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -203,19 +203,16 @@ private void addCollectionKey(
203203
PersistenceContext persistenceContext) {
204204
if ( o instanceof PersistentCollection ) {
205205
final CollectionPersister collectionPersister = pluralAttributeMapping.getCollectionDescriptor();
206-
final CollectionKey collectionKey = new CollectionKey(
206+
final Object key = ( (AbstractEntityPersister) getPersister() ).getCollectionKey(
207207
collectionPersister,
208-
( (AbstractEntityPersister) getPersister() ).getCollectionKey(
209-
collectionPersister,
210-
getInstance(),
211-
persistenceContext.getEntry( getInstance() ),
212-
getSession()
213-
)
214-
);
215-
persistenceContext.addCollectionByKey(
216-
collectionKey,
217-
(PersistentCollection<?>) o
208+
getInstance(),
209+
persistenceContext.getEntry( getInstance() ),
210+
getSession()
218211
);
212+
if ( key != null ) {
213+
final CollectionKey collectionKey = new CollectionKey( collectionPersister, key );
214+
persistenceContext.addCollectionByKey( collectionKey, (PersistentCollection<?>) o );
215+
}
219216
}
220217
}
221218

hibernate-core/src/main/java/org/hibernate/collection/spi/AbstractPersistentCollection.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
import org.hibernate.type.CompositeType;
4141
import org.hibernate.type.Type;
4242

43+
import org.checkerframework.checker.nullness.qual.Nullable;
44+
4345
import static org.hibernate.pretty.MessageHelper.collectionInfoString;
4446

4547
/**
@@ -58,16 +60,16 @@ public abstract class AbstractPersistentCollection<E> implements Serializable, P
5860

5961
private transient List<DelayedOperation<E>> operationQueue;
6062
private transient boolean directlyAccessible;
61-
private Object owner;
63+
private @Nullable Object owner;
6264
private int cachedSize = -1;
6365

64-
private String role;
65-
private Object key;
66+
private @Nullable String role;
67+
private @Nullable Object key;
6668
// collections detect changes made via their public interface and mark
6769
// themselves as dirty as a performance optimization
6870
private boolean dirty;
6971
protected boolean elementRemoved;
70-
private Serializable storedSnapshot;
72+
private @Nullable Serializable storedSnapshot;
7173

7274
private String sessionFactoryUuid;
7375
private boolean allowLoadOutsideTransaction;
@@ -84,12 +86,12 @@ protected AbstractPersistentCollection(SharedSessionContractImplementor session)
8486
}
8587

8688
@Override
87-
public final String getRole() {
89+
public final @Nullable String getRole() {
8890
return role;
8991
}
9092

9193
@Override
92-
public final Object getKey() {
94+
public final @Nullable Object getKey() {
9395
return key;
9496
}
9597

@@ -120,7 +122,7 @@ public final void dirty() {
120122
}
121123

122124
@Override
123-
public final Serializable getStoredSnapshot() {
125+
public final @Nullable Serializable getStoredSnapshot() {
124126
return storedSnapshot;
125127
}
126128

@@ -1354,7 +1356,7 @@ public Object getIdentifier(Object entry, int i) {
13541356
}
13551357

13561358
@Override
1357-
public Object getOwner() {
1359+
public @Nullable Object getOwner() {
13581360
return owner;
13591361
}
13601362

hibernate-core/src/main/java/org/hibernate/collection/spi/PersistentCollection.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public interface PersistentCollection<E> extends LazyInitializable {
6262
*
6363
* @return The owner
6464
*/
65-
Object getOwner();
65+
@Nullable Object getOwner();
6666

6767
/**
6868
* Set the reference to the owning entity
@@ -405,14 +405,14 @@ default boolean needsUpdating(
405405
*
406406
* @return the current collection key value
407407
*/
408-
Object getKey();
408+
@Nullable Object getKey();
409409

410410
/**
411411
* Get the current role name
412412
*
413413
* @return the collection role name
414414
*/
415-
String getRole();
415+
@Nullable String getRole();
416416

417417
/**
418418
* Is the collection unreferenced?
@@ -461,7 +461,7 @@ default boolean isDirectlyProvidedCollection(Object collection) {
461461
*
462462
* @return The internally stored snapshot state
463463
*/
464-
Serializable getStoredSnapshot();
464+
@Nullable Serializable getStoredSnapshot();
465465

466466
/**
467467
* Mark the collection as dirty

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,8 +1034,10 @@ public void addUninitializedCollection(CollectionPersister persister, Persistent
10341034

10351035
@Override
10361036
public void addUninitializedDetachedCollection(CollectionPersister persister, PersistentCollection<?> collection) {
1037-
final CollectionEntry ce = new CollectionEntry( persister, collection.getKey() );
1038-
addCollection( collection, ce, collection.getKey() );
1037+
final Object key = collection.getKey();
1038+
assert key != null;
1039+
final CollectionEntry ce = new CollectionEntry( persister, key );
1040+
addCollection( collection, ce, key );
10391041
if ( session.getLoadQueryInfluencers().effectivelyBatchLoadable( persister ) ) {
10401042
getBatchFetchQueue().addBatchLoadableCollection( collection, ce );
10411043
}
@@ -1093,7 +1095,7 @@ private void addCollection(PersistentCollection<?> collection, CollectionPersist
10931095
@Override
10941096
public void addInitializedDetachedCollection(CollectionPersister collectionPersister, PersistentCollection<?> collection)
10951097
throws HibernateException {
1096-
if ( collection.isUnreferenced() ) {
1098+
if ( collection.isUnreferenced() || collection.getKey() == null ) {
10971099
//treat it just like a new collection
10981100
addCollection( collection, collectionPersister );
10991101
}

hibernate-core/src/main/java/org/hibernate/engine/spi/CollectionEntry.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ public CollectionEntry(PersistentCollection<?> collection, SessionFactoryImpleme
125125
ignore = false;
126126

127127
loadedKey = collection.getKey();
128-
loadedPersister = factory.getRuntimeMetamodels().getMappingMetamodel().getCollectionDescriptor( collection.getRole() );
129-
this.role = ( loadedPersister == null ? null : loadedPersister.getRole() );
128+
role = collection.getRole();
129+
loadedPersister = factory.getRuntimeMetamodels().getMappingMetamodel().getCollectionDescriptor( NullnessUtil.castNonNull( role ) );
130130

131131
snapshot = collection.getStoredSnapshot();
132132
}

hibernate-core/src/main/java/org/hibernate/event/internal/AbstractFlushingEventListener.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,8 @@ protected void postFlush(SessionImplementor session) throws HibernateException {
414414
persistenceContext.forEachCollectionEntry(
415415
(persistentCollection, collectionEntry) -> {
416416
collectionEntry.postFlush( persistentCollection );
417-
if ( collectionEntry.getLoadedPersister() == null ) {
417+
final Object key;
418+
if ( collectionEntry.getLoadedPersister() == null || ( key = collectionEntry.getLoadedKey() ) == null ) {
418419
//if the collection is dereferenced, unset its session reference and remove from the session cache
419420
//iter.remove(); //does not work, since the entrySet is not backed by the set
420421
persistentCollection.unsetSession( session );
@@ -424,7 +425,7 @@ protected void postFlush(SessionImplementor session) throws HibernateException {
424425
//otherwise recreate the mapping between the collection and its key
425426
CollectionKey collectionKey = new CollectionKey(
426427
collectionEntry.getLoadedPersister(),
427-
collectionEntry.getLoadedKey()
428+
key
428429
);
429430
persistenceContext.addCollectionByKey( collectionKey, persistentCollection );
430431
}

hibernate-core/src/main/java/org/hibernate/event/internal/WrapVisitor.java

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -123,22 +123,24 @@ else if ( attributeInterceptor != null
123123
entry,
124124
session
125125
);
126-
PersistentCollection<?> collectionInstance = persistenceContext.getCollection(
127-
new CollectionKey( persister, key )
128-
);
129-
130-
if ( collectionInstance == null ) {
131-
// the collection has not been initialized and new collection values have been assigned,
132-
// we need to be sure to delete all the collection elements before inserting the new ones
133-
collectionInstance = persister.getCollectionSemantics().instantiateWrapper(
134-
key,
135-
persister,
136-
session
126+
if ( key != null ) {
127+
PersistentCollection<?> collectionInstance = persistenceContext.getCollection(
128+
new CollectionKey( persister, key )
137129
);
138-
persistenceContext.addUninitializedCollection( persister, collectionInstance, key );
139-
final CollectionEntry collectionEntry = persistenceContext.getCollectionEntry(
140-
collectionInstance );
141-
collectionEntry.setDoremove( true );
130+
131+
if ( collectionInstance == null ) {
132+
// the collection has not been initialized and new collection values have been assigned,
133+
// we need to be sure to delete all the collection elements before inserting the new ones
134+
collectionInstance = persister.getCollectionSemantics().instantiateWrapper(
135+
key,
136+
persister,
137+
session
138+
);
139+
persistenceContext.addUninitializedCollection( persister, collectionInstance, key );
140+
final CollectionEntry collectionEntry = persistenceContext.getCollectionEntry(
141+
collectionInstance );
142+
collectionEntry.setDoremove( true );
143+
}
142144
}
143145
}
144146
}

hibernate-core/src/main/java/org/hibernate/internal/CoreMessageLogger.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1661,7 +1661,7 @@ void cannotResolveNonNullableTransientDependencies(
16611661
+ " This is likely due to unsafe use of the session (e.g. used in multiple threads concurrently, updates during entity lifecycle hooks).",
16621662
id = 479
16631663
)
1664-
String collectionNotProcessedByFlush(String role);
1664+
String collectionNotProcessedByFlush(@Nullable String role);
16651665

16661666
@LogMessage(level = WARN)
16671667
@Message(value = "A ManagedEntity was associated with a stale PersistenceContext. A ManagedEntity may only be associated with one PersistenceContext at a time; %s", id = 480)

hibernate-core/src/main/java/org/hibernate/loader/ast/internal/CollectionLoaderSubSelectFetch.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.hibernate.engine.spi.SessionFactoryImplementor;
2222
import org.hibernate.engine.spi.SharedSessionContractImplementor;
2323
import org.hibernate.engine.spi.SubselectFetch;
24+
import org.hibernate.internal.util.NullnessUtil;
2425
import org.hibernate.internal.util.collections.CollectionHelper;
2526
import org.hibernate.loader.ast.spi.CollectionLoader;
2627
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
@@ -153,7 +154,7 @@ public PersistentCollection<?> load(Object triggerKey, SharedSessionContractImpl
153154
persistenceContext,
154155
getLoadable().getCollectionDescriptor(),
155156
c,
156-
c.getKey(),
157+
NullnessUtil.castNonNull( c.getKey() ),
157158
true
158159
);
159160
}

hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@
301301
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
302302
import org.hibernate.type.spi.TypeConfiguration;
303303

304+
import org.checkerframework.checker.nullness.qual.Nullable;
305+
304306
import static java.util.Collections.emptyList;
305307
import static java.util.Collections.emptyMap;
306308
import static java.util.Collections.emptySet;
@@ -1537,6 +1539,7 @@ public Object initializeLazyProperty(String fieldName, Object entity, SharedSess
15371539
// see if there is already a collection instance associated with the session
15381540
// NOTE : can this ever happen?
15391541
final Object key = getCollectionKey( persister, entity, entry, session );
1542+
assert key != null;
15401543
PersistentCollection<?> collection = persistenceContext.getCollection( new CollectionKey( persister, key ) );
15411544
if ( collection == null ) {
15421545
collection = collectionType.instantiate( session, persister, key );
@@ -1605,7 +1608,7 @@ public Object initializeLazyProperty(String fieldName, Object entity, SharedSess
16051608

16061609
}
16071610

1608-
public Object getCollectionKey(
1611+
public @Nullable Object getCollectionKey(
16091612
CollectionPersister persister,
16101613
Object owner,
16111614
EntityEntry ownerEntry,

hibernate-core/src/main/java/org/hibernate/type/CollectionType.java

Lines changed: 6 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848

4949
import org.jboss.logging.Logger;
5050

51+
import org.checkerframework.checker.nullness.qual.Nullable;
52+
5153
/**
5254
* A type that handles Hibernate {@code PersistentCollection}s (including arrays).
5355
*
@@ -366,7 +368,7 @@ public ForeignKeyDirection getForeignKeyDirection() {
366368
* @param session The session from which the request is originating.
367369
* @return The collection owner's key
368370
*/
369-
public Object getKeyOfOwner(Object owner, SharedSessionContractImplementor session) {
371+
public @Nullable Object getKeyOfOwner(Object owner, SharedSessionContractImplementor session) {
370372
final PersistenceContext pc = session.getPersistenceContextInternal();
371373

372374
final EntityEntry entityEntry = pc.getEntry( owner );
@@ -380,29 +382,10 @@ public Object getKeyOfOwner(Object owner, SharedSessionContractImplementor sessi
380382
return entityEntry.getId();
381383
}
382384
else {
383-
// TODO: at the point where we are resolving collection references, we don't
384-
// know if the uk value has been resolved (depends if it was earlier or
385-
// later in the mapping document) - now, we could try and use e.getStatus()
386-
// to decide to semiResolve(), trouble is that initializeEntity() reuses
387-
// the same array for resolved and hydrated values
388385
final Object loadedValue = entityEntry.getLoadedValue( foreignKeyPropertyName );
389-
final Object id = loadedValue == null ?
390-
entityEntry.getPersister().getPropertyValue( owner, foreignKeyPropertyName ) :
391-
loadedValue;
392-
393-
// NOTE VERY HACKISH WORKAROUND!!
394-
// TODO: Fix this so it will work for non-POJO entity mode
395-
Type keyType = getPersister( session ).getKeyType();
396-
if ( !keyType.getReturnedClass().isInstance( id ) ) {
397-
throw new UnsupportedOperationException( "Re-work support for semi-resolve" );
398-
// id = keyType.semiResolve(
399-
// entityEntry.getLoadedValue( foreignKeyPropertyName ),
400-
// session,
401-
// owner
402-
// );
403-
}
404-
405-
return id;
386+
return loadedValue == null
387+
? entityEntry.getPersister().getPropertyValue( owner, foreignKeyPropertyName )
388+
: loadedValue;
406389
}
407390
}
408391

0 commit comments

Comments
 (0)