Skip to content

Commit 010c6dc

Browse files
committed
HHH-18326 Use instance identity to track persistent collections
1 parent 62141c8 commit 010c6dc

File tree

5 files changed

+239
-17
lines changed

5 files changed

+239
-17
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public abstract class AbstractPersistentCollection<E> implements Serializable, P
7979
private String sessionFactoryUuid;
8080
private boolean allowLoadOutsideTransaction;
8181

82+
private int instanceId;
83+
8284
/**
8385
* Not called by Hibernate, but used by non-JDK serialization,
8486
* eg. SOAP libraries.
@@ -1362,4 +1364,13 @@ public void setOwner(Object owner) {
13621364
this.owner = owner;
13631365
}
13641366

1367+
@Override
1368+
public int $$_hibernate_getInstanceId() {
1369+
return instanceId;
1370+
}
1371+
1372+
@Override
1373+
public void $$_hibernate_setInstanceId(int instanceId) {
1374+
this.instanceId = instanceId;
1375+
}
13651376
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import org.hibernate.HibernateException;
1313
import org.hibernate.Incubating;
14+
import org.hibernate.engine.spi.InstanceIdentity;
1415
import org.hibernate.engine.spi.SharedSessionContractImplementor;
1516
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
1617
import org.hibernate.persister.collection.CollectionPersister;
@@ -52,7 +53,7 @@
5253
* @author Gavin King
5354
*/
5455
@Incubating
55-
public interface PersistentCollection<E> extends LazyInitializable {
56+
public interface PersistentCollection<E> extends LazyInitializable, InstanceIdentity {
5657
/**
5758
* Get the owning entity. Note that the owner is only
5859
* set during the flush cycle, and when a new collection

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

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@
5555
import org.hibernate.event.spi.PostLoadEventListener;
5656
import org.hibernate.internal.CoreMessageLogger;
5757
import org.hibernate.internal.util.collections.CollectionHelper;
58-
import org.hibernate.internal.util.collections.IdentityMap;
58+
import org.hibernate.internal.util.collections.InstanceIdentityMap;
59+
import org.hibernate.internal.util.collections.StandardStack;
5960
import org.hibernate.metamodel.spi.MappingMetamodelImplementor;
6061
import org.hibernate.persister.collection.CollectionPersister;
6162
import org.hibernate.persister.entity.EntityPersister;
@@ -131,7 +132,11 @@ the following fields are used in all circumstances, and are not worth (or not su
131132
private IdentityHashMap<Object, PersistentCollection<?>> arrayHolders;
132133

133134
// Identity map of CollectionEntry instances, by the collection wrapper
134-
private IdentityMap<PersistentCollection<?>, CollectionEntry> collectionEntries;
135+
private InstanceIdentityMap<PersistentCollection<?>, CollectionEntry> collectionEntries;
136+
137+
// current collection instance id and stack of reusable ones
138+
private StandardStack<Integer> reusableCollectionInstanceIds = null;
139+
private int currentCollectionInstanceId;
135140

136141
// Collection wrappers, by the CollectionKey
137142
private HashMap<CollectionKey, PersistentCollection<?>> collectionsByKey;
@@ -255,7 +260,7 @@ public void clear() {
255260

256261
final SharedSessionContractImplementor session = getSession();
257262
if ( collectionEntries != null ) {
258-
IdentityMap.onEachKey( collectionEntries, k -> k.unsetSession( session ) );
263+
collectionEntries.forEach( (k, v) -> k.unsetSession( session ) );
259264
}
260265

261266
arrayHolders = null;
@@ -267,6 +272,8 @@ public void clear() {
267272
collectionsByKey = null;
268273
nonlazyCollections = null;
269274
collectionEntries = null;
275+
currentCollectionInstanceId = 0;
276+
reusableCollectionInstanceIds = null;
270277
unownedCollections = null;
271278
nullifiableEntityKeys = null;
272279
deletedUnloadedEntityKeys = null;
@@ -1082,7 +1089,6 @@ public void replaceCollection(CollectionPersister persister, PersistentCollectio
10821089
"Replacement of not directly accessible collection found: " + oldCollection.getRole() );
10831090
}
10841091
assert !collection.isDirectlyAccessible();
1085-
final IdentityMap<PersistentCollection<?>, CollectionEntry> collectionEntries = getOrInitializeCollectionEntries();
10861092
final CollectionEntry oldEntry = collectionEntries.remove( oldCollection );
10871093
final CollectionEntry entry;
10881094
if ( oldEntry.getLoadedPersister() != null ) {
@@ -1093,7 +1099,7 @@ public void replaceCollection(CollectionPersister persister, PersistentCollectio
10931099
// A newly wrapped collection
10941100
entry = new CollectionEntry( persister, collection );
10951101
}
1096-
collectionEntries.put( collection, entry );
1102+
putCollectionEntry( collection, entry );
10971103
final Object key = collection.getKey();
10981104
if ( key != null ) {
10991105
final CollectionKey collectionKey = new CollectionKey( entry.getLoadedPersister(), key );
@@ -1112,7 +1118,7 @@ public void replaceCollection(CollectionPersister persister, PersistentCollectio
11121118
* @param key The key of the collection's entry.
11131119
*/
11141120
private void addCollection(PersistentCollection<?> coll, CollectionEntry entry, Object key) {
1115-
getOrInitializeCollectionEntries().put( coll, entry );
1121+
putCollectionEntry( coll, entry );
11161122
final CollectionKey collectionKey = new CollectionKey( entry.getLoadedPersister(), key );
11171123
final PersistentCollection<?> old = addCollectionByKey( collectionKey, coll );
11181124
if ( old != null ) {
@@ -1125,11 +1131,12 @@ private void addCollection(PersistentCollection<?> coll, CollectionEntry entry,
11251131
}
11261132
}
11271133

1128-
private IdentityMap<PersistentCollection<?>, CollectionEntry> getOrInitializeCollectionEntries() {
1134+
private void putCollectionEntry(PersistentCollection<?> collection, CollectionEntry entry) {
11291135
if ( this.collectionEntries == null ) {
1130-
this.collectionEntries = IdentityMap.instantiateSequenced( INIT_COLL_SIZE );
1136+
this.collectionEntries = new InstanceIdentityMap<>();
11311137
}
1132-
return this.collectionEntries;
1138+
collection.$$_hibernate_setInstanceId( nextCollectionInstanceId() );
1139+
this.collectionEntries.put( collection, entry );
11331140
}
11341141

11351142
/**
@@ -1140,7 +1147,7 @@ private IdentityMap<PersistentCollection<?>, CollectionEntry> getOrInitializeCol
11401147
*/
11411148
private void addCollection(PersistentCollection<?> collection, CollectionPersister persister) {
11421149
final CollectionEntry ce = new CollectionEntry( persister, collection );
1143-
getOrInitializeCollectionEntries().put( collection, ce );
1150+
putCollectionEntry( collection, ce );
11441151
}
11451152

11461153
@Override
@@ -1375,11 +1382,17 @@ public int getNumberOfManagedEntities() {
13751382
return collectionEntries;
13761383
}
13771384

1385+
private int nextCollectionInstanceId() {
1386+
return reusableCollectionInstanceIds != null && !reusableCollectionInstanceIds.isEmpty() ?
1387+
reusableCollectionInstanceIds.pop() :
1388+
currentCollectionInstanceId++;
1389+
}
1390+
13781391
@Override
13791392
public void forEachCollectionEntry(BiConsumer<PersistentCollection<?>, CollectionEntry> action, boolean concurrent) {
13801393
if ( collectionEntries != null ) {
13811394
if ( concurrent ) {
1382-
for ( Entry<PersistentCollection<?>,CollectionEntry> entry : IdentityMap.concurrentEntries( collectionEntries ) ) {
1395+
for ( Entry<PersistentCollection<?>,CollectionEntry> entry : collectionEntries.toArray() ) {
13831396
action.accept( entry.getKey(), entry.getValue() );
13841397
}
13851398
}
@@ -2032,7 +2045,7 @@ public static StatefulPersistenceContext deserialize(
20322045
final PersistentCollection<?> pc = (PersistentCollection<?>) ois.readObject();
20332046
final CollectionEntry ce = CollectionEntry.deserialize( ois, session );
20342047
pc.setCurrentSession( session );
2035-
rtn.getOrInitializeCollectionEntries().put( pc, ce );
2048+
rtn.putCollectionEntry( pc, ce );
20362049
}
20372050

20382051
count = ois.readInt();
@@ -2174,7 +2187,17 @@ public int getCollectionEntriesSize() {
21742187

21752188
@Override
21762189
public CollectionEntry removeCollectionEntry(PersistentCollection<?> collection) {
2177-
return collectionEntries == null ? null : collectionEntries.remove(collection);
2190+
if ( collectionEntries != null ) {
2191+
final int instanceId = collection.$$_hibernate_getInstanceId();
2192+
if ( reusableCollectionInstanceIds == null ) {
2193+
reusableCollectionInstanceIds = new StandardStack<>();
2194+
}
2195+
reusableCollectionInstanceIds.push( instanceId );
2196+
return collectionEntries.remove( collection );
2197+
}
2198+
else {
2199+
return null;
2200+
}
21782201
}
21792202

21802203
@Override

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
import org.hibernate.event.spi.PersistContext;
3535
import org.hibernate.internal.CoreMessageLogger;
3636
import org.hibernate.internal.util.EntityPrinter;
37-
import org.hibernate.internal.util.collections.IdentityMap;
37+
import org.hibernate.internal.util.collections.InstanceIdentityMap;
3838
import org.hibernate.persister.entity.EntityPersister;
3939

4040
import org.jboss.logging.Logger;
@@ -190,7 +190,7 @@ private void prepareCollectionFlushes(PersistenceContext persistenceContext) thr
190190
persistenceContext.getCollectionEntries();
191191
if ( collectionEntries != null ) {
192192
for ( Map.Entry<PersistentCollection<?>, CollectionEntry> entry :
193-
( (IdentityMap<PersistentCollection<?>, CollectionEntry>) collectionEntries ).entryArray() ) {
193+
( (InstanceIdentityMap<PersistentCollection<?>, CollectionEntry>) collectionEntries ).toArray() ) {
194194
entry.getValue().preFlush( entry.getKey() );
195195
}
196196
}
@@ -271,7 +271,7 @@ private int flushCollections(final EventSource session, final PersistenceContext
271271
}
272272
else {
273273
count = collectionEntries.size();
274-
for ( Map.Entry<PersistentCollection<?>, CollectionEntry> me : ( (IdentityMap<PersistentCollection<?>, CollectionEntry>) collectionEntries ).entryArray() ) {
274+
for ( Map.Entry<PersistentCollection<?>, CollectionEntry> me : ( (InstanceIdentityMap<PersistentCollection<?>, CollectionEntry>) collectionEntries ).toArray() ) {
275275
final CollectionEntry ce = me.getValue();
276276
if ( !ce.isReached() && !ce.isIgnore() ) {
277277
Collections.processUnreachableCollection( me.getKey(), session );
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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.pc;
6+
7+
import jakarta.persistence.ElementCollection;
8+
import jakarta.persistence.Entity;
9+
import jakarta.persistence.Id;
10+
import jakarta.persistence.OneToMany;
11+
import org.hibernate.annotations.Immutable;
12+
import org.hibernate.collection.spi.PersistentCollection;
13+
import org.hibernate.engine.spi.ManagedEntity;
14+
import org.hibernate.testing.bytecode.enhancement.extension.BytecodeEnhanced;
15+
import org.hibernate.testing.orm.junit.DomainModel;
16+
import org.hibernate.testing.orm.junit.SessionFactory;
17+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
18+
import org.junit.jupiter.api.AfterEach;
19+
import org.junit.jupiter.api.Test;
20+
21+
import java.util.ArrayList;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
28+
@DomainModel(annotatedClasses = {
29+
InstanceIdentityTest.ImmutableEntity.class,
30+
InstanceIdentityTest.EntityWithCollections.class,
31+
})
32+
@SessionFactory
33+
@BytecodeEnhanced
34+
public class InstanceIdentityTest {
35+
@Test
36+
public void testEnhancedImmutableEntity(SessionFactoryScope scope) {
37+
scope.inTransaction( session -> {
38+
final ImmutableEntity entity1 = new ImmutableEntity( 1, "entity_1" );
39+
session.persist( entity1 );
40+
41+
// false warning, bytecode enhancement of the test class will make the cast work
42+
assertThat( ((ManagedEntity) entity1).$$_hibernate_getInstanceId() ).isGreaterThanOrEqualTo( 0 );
43+
assertThat( session.contains( entity1 ) ).isTrue();
44+
45+
final ImmutableEntity entity2 = new ImmutableEntity( 2, "entity_2" );
46+
session.persist( entity2 );
47+
final ImmutableEntity entity3 = new ImmutableEntity( 3, "entity_3" );
48+
session.persist( entity3 );
49+
} );
50+
51+
scope.inSession( session -> {
52+
assertThat( session.find( ImmutableEntity.class, 1 ) ).isNotNull().extracting( ImmutableEntity::getName )
53+
.isEqualTo( "entity_1" );
54+
55+
final List<ImmutableEntity> immutableEntities = session.createQuery(
56+
"from ImmutableEntity",
57+
ImmutableEntity.class
58+
).getResultList();
59+
60+
assertThat( immutableEntities ).hasSize( 3 );
61+
62+
// test find again, this time from 1st level cache
63+
final ImmutableEntity entity2 = session.find( ImmutableEntity.class, 2 );
64+
assertThat( entity2 ).isNotNull().extracting( ImmutableEntity::getName )
65+
.isEqualTo( "entity_2" );
66+
67+
session.detach( entity2 );
68+
assertThat( session.contains( entity2 ) ).isFalse();
69+
} );
70+
}
71+
72+
@Test
73+
public void testPersistentCollections(SessionFactoryScope scope) {
74+
scope.inTransaction( session -> {
75+
final ImmutableEntity immutableEntity = new ImmutableEntity( 4, "entity_4" );
76+
session.persist( immutableEntity );
77+
final EntityWithCollections entity = new EntityWithCollections( 1 );
78+
entity.getStringList().addAll( List.of( "one", "two" ) );
79+
entity.getEntityMap().put( "4", immutableEntity );
80+
session.persist( entity );
81+
82+
assertThat( entity.getStringList() ).isInstanceOf( PersistentCollection.class );
83+
final PersistentCollection<?> persistentList = (PersistentCollection<?>) entity.getStringList();
84+
assertThat( persistentList.$$_hibernate_getInstanceId() ).isGreaterThanOrEqualTo( 0 );
85+
86+
assertThat( entity.getEntityMap() ).isInstanceOf( PersistentCollection.class );
87+
final PersistentCollection<?> persistentMap = (PersistentCollection<?>) entity.getEntityMap();
88+
assertThat( persistentMap.$$_hibernate_getInstanceId() ).isGreaterThanOrEqualTo( 0 );
89+
90+
assertThat( session.getPersistenceContextInternal().getCollectionEntries() ).isNotNull()
91+
.containsKeys( persistentList, persistentMap );
92+
} );
93+
94+
scope.inTransaction( session -> {
95+
final EntityWithCollections entity = session.find( EntityWithCollections.class, 1 );
96+
entity.getStringList().add( "three" );
97+
entity.getEntityMap().clear();
98+
} );
99+
100+
scope.inSession( session -> {
101+
final EntityWithCollections entity = session.find( EntityWithCollections.class, 1 );
102+
assertThat( entity.getStringList() ).hasSize( 3 ).containsExactly( "one", "two", "three" );
103+
assertThat( entity.getEntityMap() ).isEmpty();
104+
} );
105+
}
106+
107+
@AfterEach
108+
public void tearDown(SessionFactoryScope scope) {
109+
scope.getSessionFactory().getSchemaManager().truncateMappedObjects();
110+
}
111+
112+
@Immutable
113+
@Entity(name = "ImmutableEntity")
114+
static class ImmutableEntity {
115+
@Id
116+
private Integer id;
117+
118+
private String name;
119+
120+
public ImmutableEntity() {
121+
}
122+
123+
public ImmutableEntity(Integer id, String name) {
124+
this.id = id;
125+
this.name = name;
126+
}
127+
128+
public Integer getId() {
129+
return id;
130+
}
131+
132+
public void setId(Integer id) {
133+
this.id = id;
134+
}
135+
136+
public String getName() {
137+
return name;
138+
}
139+
140+
public void setName(String name) {
141+
this.name = name;
142+
}
143+
}
144+
145+
@Entity(name = "EntityWithCollections")
146+
static class EntityWithCollections {
147+
@Id
148+
private Integer id;
149+
150+
@ElementCollection
151+
private List<String> stringList = new ArrayList<>();
152+
153+
@OneToMany
154+
private Map<String, ImmutableEntity> entityMap = new HashMap<>();
155+
156+
public EntityWithCollections() {
157+
}
158+
159+
public EntityWithCollections(Integer id) {
160+
this.id = id;
161+
}
162+
163+
public Integer getId() {
164+
return id;
165+
}
166+
167+
public void setId(Integer id) {
168+
this.id = id;
169+
}
170+
171+
public List<String> getStringList() {
172+
return stringList;
173+
}
174+
175+
public void setStringList(List<String> stringList) {
176+
this.stringList = stringList;
177+
}
178+
179+
public Map<String, ImmutableEntity> getEntityMap() {
180+
return entityMap;
181+
}
182+
183+
public void setEntityMap(Map<String, ImmutableEntity> entityMap) {
184+
this.entityMap = entityMap;
185+
}
186+
}
187+
}

0 commit comments

Comments
 (0)