Skip to content

Commit a894991

Browse files
soul2zimatebeikov
authored andcommitted
HHH-18229 Handle null owner key for collections
(cherry picked from commit 1f08501)
1 parent 283e27c commit a894991

13 files changed

+413
-70
lines changed

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

+8-11
Original file line numberDiff line numberDiff line change
@@ -201,19 +201,16 @@ private void addCollectionKey(
201201
PersistenceContext persistenceContext) {
202202
if ( o instanceof PersistentCollection ) {
203203
final CollectionPersister collectionPersister = pluralAttributeMapping.getCollectionDescriptor();
204-
final CollectionKey collectionKey = new CollectionKey(
204+
final Object key = ( (AbstractEntityPersister) getPersister() ).getCollectionKey(
205205
collectionPersister,
206-
( (AbstractEntityPersister) getPersister() ).getCollectionKey(
207-
collectionPersister,
208-
getInstance(),
209-
persistenceContext.getEntry( getInstance() ),
210-
getSession()
211-
)
212-
);
213-
persistenceContext.addCollectionByKey(
214-
collectionKey,
215-
(PersistentCollection<?>) o
206+
getInstance(),
207+
persistenceContext.getEntry( getInstance() ),
208+
getSession()
216209
);
210+
if ( key != null ) {
211+
final CollectionKey collectionKey = new CollectionKey( collectionPersister, key );
212+
persistenceContext.addCollectionByKey( collectionKey, (PersistentCollection<?>) o );
213+
}
217214
}
218215
}
219216

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

+13-8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.util.Map;
1818
import java.util.UUID;
1919

20+
import org.checkerframework.checker.nullness.qual.Nullable;
2021
import org.hibernate.AssertionFailure;
2122
import org.hibernate.FlushMode;
2223
import org.hibernate.HibernateException;
@@ -42,6 +43,10 @@
4243
import org.hibernate.type.CompositeType;
4344
import org.hibernate.type.Type;
4445

46+
import org.checkerframework.checker.nullness.qual.Nullable;
47+
48+
import static org.hibernate.pretty.MessageHelper.collectionInfoString;
49+
4550
/**
4651
* Base class implementing {@link PersistentCollection}
4752
*
@@ -58,16 +63,16 @@ public abstract class AbstractPersistentCollection<E> implements Serializable, P
5863

5964
private transient List<DelayedOperation<E>> operationQueue;
6065
private transient boolean directlyAccessible;
61-
private Object owner;
66+
private @Nullable Object owner;
6267
private int cachedSize = -1;
6368

64-
private String role;
65-
private Object key;
69+
private @Nullable String role;
70+
private @Nullable Object key;
6671
// collections detect changes made via their public interface and mark
6772
// themselves as dirty as a performance optimization
6873
private boolean dirty;
6974
protected boolean elementRemoved;
70-
private Serializable storedSnapshot;
75+
private @Nullable Serializable storedSnapshot;
7176

7277
private String sessionFactoryUuid;
7378
private boolean allowLoadOutsideTransaction;
@@ -84,12 +89,12 @@ protected AbstractPersistentCollection(SharedSessionContractImplementor session)
8489
}
8590

8691
@Override
87-
public final String getRole() {
92+
public final @Nullable String getRole() {
8893
return role;
8994
}
9095

9196
@Override
92-
public final Object getKey() {
97+
public final @Nullable Object getKey() {
9398
return key;
9499
}
95100

@@ -120,7 +125,7 @@ public final void dirty() {
120125
}
121126

122127
@Override
123-
public final Serializable getStoredSnapshot() {
128+
public final @Nullable Serializable getStoredSnapshot() {
124129
return storedSnapshot;
125130
}
126131

@@ -1351,7 +1356,7 @@ public Object getIdentifier(Object entry, int i) {
13511356
}
13521357

13531358
@Override
1354-
public Object getOwner() {
1359+
public @Nullable Object getOwner() {
13551360
return owner;
13561361
}
13571362

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

+5-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.Iterator;
1212
import java.util.List;
1313

14+
import org.checkerframework.checker.nullness.qual.Nullable;
1415
import org.hibernate.HibernateException;
1516
import org.hibernate.Incubating;
1617
import org.hibernate.engine.spi.SharedSessionContractImplementor;
@@ -60,7 +61,7 @@ public interface PersistentCollection<E> extends LazyInitializable {
6061
*
6162
* @return The owner
6263
*/
63-
Object getOwner();
64+
@Nullable Object getOwner();
6465

6566
/**
6667
* Set the reference to the owning entity
@@ -403,14 +404,14 @@ default boolean needsUpdating(
403404
*
404405
* @return the current collection key value
405406
*/
406-
Object getKey();
407+
@Nullable Object getKey();
407408

408409
/**
409410
* Get the current role name
410411
*
411412
* @return the collection role name
412413
*/
413-
String getRole();
414+
@Nullable String getRole();
414415

415416
/**
416417
* Is the collection unreferenced?
@@ -459,7 +460,7 @@ default boolean isDirectlyProvidedCollection(Object collection) {
459460
*
460461
* @return The internally stored snapshot state
461462
*/
462-
Serializable getStoredSnapshot();
463+
@Nullable Serializable getStoredSnapshot();
463464

464465
/**
465466
* Mark the collection as dirty

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -875,8 +875,10 @@ public void addUninitializedCollection(CollectionPersister persister, Persistent
875875

876876
@Override
877877
public void addUninitializedDetachedCollection(CollectionPersister persister, PersistentCollection<?> collection) {
878-
final CollectionEntry ce = new CollectionEntry( persister, collection.getKey() );
879-
addCollection( collection, ce, collection.getKey() );
878+
final Object key = collection.getKey();
879+
assert key != null;
880+
final CollectionEntry ce = new CollectionEntry( persister, key );
881+
addCollection( collection, ce, key );
880882
if ( persister.getBatchSize() > 1 ) {
881883
getBatchFetchQueue().addBatchLoadableCollection( collection, ce );
882884
}
@@ -934,7 +936,7 @@ private void addCollection(PersistentCollection<?> collection, CollectionPersist
934936
@Override
935937
public void addInitializedDetachedCollection(CollectionPersister collectionPersister, PersistentCollection<?> collection)
936938
throws HibernateException {
937-
if ( collection.isUnreferenced() ) {
939+
if ( collection.isUnreferenced() || collection.getKey() == null ) {
938940
//treat it just like a new collection
939941
addCollection( collection, collectionPersister );
940942
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.hibernate.collection.spi.PersistentCollection;
1919
import org.hibernate.internal.CoreLogging;
2020
import org.hibernate.internal.CoreMessageLogger;
21+
import org.hibernate.internal.util.NullnessUtil;
2122
import org.hibernate.persister.collection.CollectionPersister;
2223
import org.hibernate.pretty.MessageHelper;
2324

@@ -118,8 +119,9 @@ public CollectionEntry(PersistentCollection<?> collection, SessionFactoryImpleme
118119
ignore = false;
119120

120121
loadedKey = collection.getKey();
122+
role = collection.getRole();
121123
setLoadedPersister(
122-
factory.getRuntimeMetamodels().getMappingMetamodel().getCollectionDescriptor( collection.getRole() )
124+
factory.getRuntimeMetamodels().getMappingMetamodel().getCollectionDescriptor( NullnessUtil.castNonNull( role ) )
123125
);
124126

125127
snapshot = collection.getStoredSnapshot();

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,8 @@ protected void postFlush(SessionImplementor session) throws HibernateException {
407407
persistenceContext.forEachCollectionEntry(
408408
(persistentCollection, collectionEntry) -> {
409409
collectionEntry.postFlush( persistentCollection );
410-
if ( collectionEntry.getLoadedPersister() == null ) {
410+
final Object key;
411+
if ( collectionEntry.getLoadedPersister() == null || ( key = collectionEntry.getLoadedKey() ) == null ) {
411412
//if the collection is dereferenced, unset its session reference and remove from the session cache
412413
//iter.remove(); //does not work, since the entrySet is not backed by the set
413414
persistentCollection.unsetSession( session );
@@ -417,7 +418,7 @@ protected void postFlush(SessionImplementor session) throws HibernateException {
417418
//otherwise recreate the mapping between the collection and its key
418419
CollectionKey collectionKey = new CollectionKey(
419420
collectionEntry.getLoadedPersister(),
420-
collectionEntry.getLoadedKey()
421+
key
421422
);
422423
persistenceContext.addCollectionByKey( collectionKey, persistentCollection );
423424
}

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

+17-15
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 =
141+
persistenceContext.getCollectionEntry( collectionInstance );
142+
collectionEntry.setDoremove( true );
143+
}
142144
}
143145
}
144146
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import javax.naming.NameNotFoundException;
2121
import javax.naming.NamingException;
2222

23+
import org.checkerframework.checker.nullness.qual.Nullable;
2324
import org.hibernate.HibernateException;
2425
import org.hibernate.LockMode;
2526
import org.hibernate.cache.CacheException;
@@ -1690,7 +1691,7 @@ void cannotResolveNonNullableTransientDependencies(
16901691
+ " This is likely due to unsafe use of the session (e.g. used in multiple threads concurrently, updates during entity lifecycle hooks).",
16911692
id = 479
16921693
)
1693-
String collectionNotProcessedByFlush(String role);
1694+
String collectionNotProcessedByFlush(@Nullable String role);
16941695

16951696
@LogMessage(level = WARN)
16961697
@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

+2-1
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;
@@ -147,7 +148,7 @@ public PersistentCollection<?> load(Object triggerKey, SharedSessionContractImpl
147148
persistenceContext,
148149
getLoadable().getCollectionDescriptor(),
149150
c,
150-
c.getKey(),
151+
NullnessUtil.castNonNull( c.getKey() ),
151152
true
152153
);
153154
}

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.function.Consumer;
3030
import java.util.function.Supplier;
3131

32+
import org.checkerframework.checker.nullness.qual.Nullable;
3233
import org.hibernate.AssertionFailure;
3334
import org.hibernate.FetchMode;
3435
import org.hibernate.Filter;
@@ -292,6 +293,8 @@
292293
import org.hibernate.type.descriptor.java.spi.JavaTypeRegistry;
293294
import org.hibernate.type.spi.TypeConfiguration;
294295

296+
import org.checkerframework.checker.nullness.qual.Nullable;
297+
295298
import static java.util.Collections.emptyList;
296299
import static java.util.Collections.emptyMap;
297300
import static java.util.Collections.emptySet;
@@ -1463,6 +1466,7 @@ public Object initializeLazyProperty(String fieldName, Object entity, SharedSess
14631466
// see if there is already a collection instance associated with the session
14641467
// NOTE : can this ever happen?
14651468
final Object key = getCollectionKey( persister, entity, entry, session );
1469+
assert key != null;
14661470
PersistentCollection<?> collection = persistenceContext.getCollection( new CollectionKey( persister, key ) );
14671471
if ( collection == null ) {
14681472
collection = collectionType.instantiate( session, persister, key );
@@ -1531,7 +1535,7 @@ public Object initializeLazyProperty(String fieldName, Object entity, SharedSess
15311535

15321536
}
15331537

1534-
public Object getCollectionKey(
1538+
public @Nullable Object getCollectionKey(
15351539
CollectionPersister persister,
15361540
Object owner,
15371541
EntityEntry ownerEntry,

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

+8-23
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.SortedMap;
2020
import java.util.TreeMap;
2121

22+
import org.checkerframework.checker.nullness.qual.Nullable;
2223
import org.hibernate.Hibernate;
2324
import org.hibernate.HibernateException;
2425
import org.hibernate.MappingException;
@@ -48,6 +49,8 @@
4849

4950
import org.jboss.logging.Logger;
5051

52+
import org.checkerframework.checker.nullness.qual.Nullable;
53+
5154
/**
5255
* A type that handles Hibernate {@code PersistentCollection}s (including arrays).
5356
*
@@ -366,7 +369,7 @@ public ForeignKeyDirection getForeignKeyDirection() {
366369
* @param session The session from which the request is originating.
367370
* @return The collection owner's key
368371
*/
369-
public Object getKeyOfOwner(Object owner, SharedSessionContractImplementor session) {
372+
public @Nullable Object getKeyOfOwner(Object owner, SharedSessionContractImplementor session) {
370373
final PersistenceContext pc = session.getPersistenceContextInternal();
371374

372375
EntityEntry entityEntry = pc.getEntry( owner );
@@ -380,28 +383,10 @@ public Object getKeyOfOwner(Object owner, SharedSessionContractImplementor sessi
380383
return entityEntry.getId();
381384
}
382385
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
388-
Object id = entityEntry.getLoadedState() != null
389-
? entityEntry.getLoadedValue( foreignKeyPropertyName )
390-
: entityEntry.getPersister().getPropertyValue( owner, foreignKeyPropertyName );
391-
392-
// NOTE VERY HACKISH WORKAROUND!!
393-
// TODO: Fix this so it will work for non-POJO entity mode
394-
Type keyType = getPersister( session ).getKeyType();
395-
if ( !keyType.getReturnedClass().isInstance( id ) ) {
396-
throw new UnsupportedOperationException( "Re-work support for semi-resolve" );
397-
// id = keyType.semiResolve(
398-
// entityEntry.getLoadedValue( foreignKeyPropertyName ),
399-
// session,
400-
// owner
401-
// );
402-
}
403-
404-
return id;
386+
final Object loadedValue = entityEntry.getLoadedValue( foreignKeyPropertyName );
387+
return loadedValue == null
388+
? entityEntry.getPersister().getPropertyValue( owner, foreignKeyPropertyName )
389+
: loadedValue;
405390
}
406391
}
407392

0 commit comments

Comments
 (0)