Skip to content

Commit cc76f75

Browse files
committed
HHH-19070 HHH-18326 Reset entity instance ids, use setPersistenceInfo
1 parent 6bc23b8 commit cc76f75

File tree

3 files changed

+117
-46
lines changed

3 files changed

+117
-46
lines changed

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

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class EntityEntryContext {
5252
private final transient PersistenceContext persistenceContext;
5353

5454
private transient InstanceIdentityStore<ImmutableManagedEntityHolder> immutableManagedEntityXref;
55-
private transient int currentInstanceId;
55+
private transient int currentInstanceId = 1;
5656

5757
private transient ManagedEntity head;
5858
private transient ManagedEntity tail;
@@ -97,10 +97,13 @@ public void addEntityEntry(Object entity, EntityEntry entityEntry) {
9797
// Throw an exception if entity is a mutable ManagedEntity that is associated with a different
9898
// PersistenceContext.
9999
ManagedEntity managedEntity = getAssociatedManagedEntity( entity );
100+
101+
int instanceId = nextManagedEntityInstanceId();
100102
final boolean alreadyAssociated = managedEntity != null;
101103
if ( !alreadyAssociated ) {
102104
if ( isManagedEntity( entity ) ) {
103105
final ManagedEntity managed = asManagedEntity( entity );
106+
assert managed.$$_hibernate_getInstanceId() == 0;
104107
if ( entityEntry.getPersister().isMutable() ) {
105108
managedEntity = managed;
106109
// We know that managedEntity is not associated with the same PersistenceContext.
@@ -111,11 +114,11 @@ public void addEntityEntry(Object entity, EntityEntry entityEntry) {
111114
// Create a holder for PersistenceContext-related data.
112115
managedEntity = new ImmutableManagedEntityHolder( managed );
113116
if ( !isReferenceCachingEnabled( entityEntry.getPersister() ) ) {
114-
managed.$$_hibernate_setInstanceId( nextManagedEntityInstanceId() );
115-
putImmutableManagedEntity( managed, (ImmutableManagedEntityHolder) managedEntity );
117+
putImmutableManagedEntity( managed, instanceId, (ImmutableManagedEntityHolder) managedEntity );
116118
}
117119
else {
118120
// When reference caching is enabled we cannot set the instance-id on the entity instance
121+
instanceId = 0;
119122
putManagedEntity( entity, managedEntity );
120123
}
121124
}
@@ -126,34 +129,33 @@ public void addEntityEntry(Object entity, EntityEntry entityEntry) {
126129
}
127130
}
128131

129-
// associate the EntityEntry with the entity
130-
managedEntity.$$_hibernate_setEntityEntry( entityEntry );
131-
132132
if ( alreadyAssociated ) {
133133
// if the entity was already associated with the context, skip the linking step.
134+
managedEntity.$$_hibernate_setEntityEntry( entityEntry );
134135
return;
135136
}
136137

137138
// TODO: can dirty be set to true here?
138139

139140
// finally, set up linking and count
141+
final ManagedEntity previous;
140142
if ( tail == null ) {
141143
assert head == null;
142-
// Protect against stale data in the ManagedEntity and nullify previous/next references.
143-
managedEntity.$$_hibernate_setPreviousManagedEntity( null );
144-
managedEntity.$$_hibernate_setNextManagedEntity( null );
144+
// Protect against stale data in the ManagedEntity and nullify previous reference.
145+
previous = null;
145146
head = managedEntity;
146147
tail = head;
147148
count = 1;
148149
}
149150
else {
150151
tail.$$_hibernate_setNextManagedEntity( managedEntity );
151-
managedEntity.$$_hibernate_setPreviousManagedEntity( tail );
152-
// Protect against stale data left in the ManagedEntity nullify next reference.
153-
managedEntity.$$_hibernate_setNextManagedEntity( null );
152+
previous = tail;
154153
tail = managedEntity;
155154
count++;
156155
}
156+
157+
// Protect against stale data left in the ManagedEntity nullify next reference.
158+
managedEntity.$$_hibernate_setPersistenceInfo( entityEntry, previous, null, instanceId );
157159
}
158160

159161
private static boolean isReferenceCachingEnabled(EntityPersister persister) {
@@ -201,11 +203,11 @@ private int nextManagedEntityInstanceId() {
201203
return currentInstanceId++;
202204
}
203205

204-
private void putImmutableManagedEntity(ManagedEntity managed, ImmutableManagedEntityHolder holder) {
206+
private void putImmutableManagedEntity(ManagedEntity managed, int instanceId, ImmutableManagedEntityHolder holder) {
205207
if ( immutableManagedEntityXref == null ) {
206208
immutableManagedEntityXref = new InstanceIdentityStore<>();
207209
}
208-
immutableManagedEntityXref.put( managed, holder );
210+
immutableManagedEntityXref.put( managed, instanceId, holder );
209211
}
210212

211213
private void checkNotAssociatedWithOtherPersistenceContextIfMutable(ManagedEntity managedEntity) {
@@ -288,12 +290,6 @@ else if ( !isManagedEntity( entity ) ) {
288290
nonEnhancedEntityXref.remove( entity );
289291
}
290292

291-
// prepare for re-linking...
292-
final ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity();
293-
final ManagedEntity next = managedEntity.$$_hibernate_getNextManagedEntity();
294-
managedEntity.$$_hibernate_setPreviousManagedEntity( null );
295-
managedEntity.$$_hibernate_setNextManagedEntity( null );
296-
297293
// re-link
298294
count--;
299295

@@ -302,11 +298,13 @@ else if ( !isManagedEntity( entity ) ) {
302298
head = null;
303299
tail = null;
304300

305-
assert previous == null;
306-
assert next == null;
301+
assert managedEntity.$$_hibernate_getPreviousManagedEntity() == null;
302+
assert managedEntity.$$_hibernate_getNextManagedEntity() == null;
307303
}
308304
else {
309305
// otherwise, previous or next (or both) should be non-null
306+
final ManagedEntity previous = managedEntity.$$_hibernate_getPreviousManagedEntity();
307+
final ManagedEntity next = managedEntity.$$_hibernate_getNextManagedEntity();
310308
if ( previous == null ) {
311309
// we are removing head
312310
assert managedEntity == head;
@@ -327,9 +325,7 @@ else if ( !isManagedEntity( entity ) ) {
327325
}
328326

329327
// finally clean out the ManagedEntity and return the associated EntityEntry
330-
final EntityEntry theEntityEntry = managedEntity.$$_hibernate_getEntityEntry();
331-
managedEntity.$$_hibernate_setEntityEntry( null );
332-
return theEntityEntry;
328+
return clearManagedEntity( managedEntity );
333329
}
334330

335331
/**
@@ -379,7 +375,7 @@ private void clearAllReferencesFromManagedEntities() {
379375
nextManagedEntity = current.$$_hibernate_getNextManagedEntity();
380376
Object toProcess = current.$$_hibernate_getEntityInstance();
381377
unsetSession( asPersistentAttributeInterceptableOrNull( toProcess ) );
382-
clearManagedEntity( current );//careful this also unlinks from the "next" entry in the list
378+
clearManagedEntity( current ); //careful this also unlinks from the "next" entry in the list
383379
}
384380
}
385381

@@ -413,13 +409,17 @@ public void clear() {
413409
count = 0;
414410

415411
reentrantSafeEntries = null;
416-
currentInstanceId = 0;
412+
currentInstanceId = 1;
417413
}
418414

419-
private static void clearManagedEntity(final ManagedEntity node) {
420-
node.$$_hibernate_setEntityEntry( null );
421-
node.$$_hibernate_setPreviousManagedEntity( null );
422-
node.$$_hibernate_setNextManagedEntity( null );
415+
/**
416+
* Resets the persistence information in a managed entity, and returns its previous {@link EntityEntry}
417+
*
418+
* @param node the managed entity to clear
419+
* @return the previous {@link EntityEntry} contained in the managed entity
420+
*/
421+
private static EntityEntry clearManagedEntity(final ManagedEntity node) {
422+
return node.$$_hibernate_setPersistenceInfo( null, null, null, 0 );
423423
}
424424

425425
/**
@@ -503,6 +503,7 @@ public static EntityEntryContext deserialize(ObjectInputStream ois, StatefulPers
503503

504504
final EntityEntry entry = deserializeEntityEntry( entityEntryClassNameArr, ois, rtn );
505505

506+
final int instanceId = context.nextManagedEntityInstanceId();
506507
final ManagedEntity managedEntity;
507508
if ( isEnhanced ) {
508509
final ManagedEntity castedEntity = asManagedEntity( entity );
@@ -512,8 +513,7 @@ public static EntityEntryContext deserialize(ObjectInputStream ois, StatefulPers
512513
else {
513514
managedEntity = new ImmutableManagedEntityHolder( castedEntity );
514515
if ( !isReferenceCachingEnabled( entry.getPersister() ) ) {
515-
castedEntity.$$_hibernate_setInstanceId( context.nextManagedEntityInstanceId() );
516-
context.putImmutableManagedEntity( castedEntity, (ImmutableManagedEntityHolder) managedEntity );
516+
context.putImmutableManagedEntity( castedEntity, instanceId, (ImmutableManagedEntityHolder) managedEntity );
517517
}
518518
else {
519519
context.putManagedEntity( entity, castedEntity );
@@ -525,16 +525,15 @@ public static EntityEntryContext deserialize(ObjectInputStream ois, StatefulPers
525525
context.putManagedEntity( entity, managedEntity );
526526
}
527527

528-
managedEntity.$$_hibernate_setEntityEntry( entry );
529-
530528
if ( previous == null ) {
531529
context.head = managedEntity;
532530
}
533531
else {
534532
previous.$$_hibernate_setNextManagedEntity( managedEntity );
535-
managedEntity.$$_hibernate_setPreviousManagedEntity( previous );
536533
}
537534

535+
managedEntity.$$_hibernate_setPersistenceInfo( entry, previous, null, instanceId );
536+
538537
previous = managedEntity;
539538
}
540539

@@ -631,12 +630,21 @@ public ManagedEntityImpl(Object entityInstance) {
631630

632631
@Override
633632
public int $$_hibernate_getInstanceId() {
634-
return -1;
633+
return 0;
635634
}
636635

637636
@Override
638637
public void $$_hibernate_setInstanceId(int id) {
639638
}
639+
640+
@Override
641+
public EntityEntry $$_hibernate_setPersistenceInfo(EntityEntry entityEntry, ManagedEntity previous, ManagedEntity next, int instanceId) {
642+
final EntityEntry oldEntry = this.entityEntry;
643+
this.entityEntry = entityEntry;
644+
this.previous = previous;
645+
this.next = next;
646+
return oldEntry;
647+
}
640648
}
641649

642650
private static class ImmutableManagedEntityHolder implements ManagedEntity {
@@ -662,7 +670,7 @@ public ImmutableManagedEntityHolder(ManagedEntity immutableManagedEntity) {
662670
public void $$_hibernate_setEntityEntry(EntityEntry entityEntry) {
663671
// need to think about implications for memory leaks here if we don't removed reference to EntityEntry
664672
if ( entityEntry == null ) {
665-
if ( canClearEntityEntryReference() ) {
673+
if ( canClearEntityEntryReference( managedEntity.$$_hibernate_getEntityEntry() ) ) {
666674
managedEntity.$$_hibernate_setEntityEntry( null );
667675
}
668676
// otherwise, do nothing.
@@ -720,10 +728,9 @@ public ImmutableManagedEntityHolder(ManagedEntity immutableManagedEntity) {
720728

721729
// Check instance type of EntityEntry and if type is ImmutableEntityEntry,
722730
// check to see if entity is referenced cached in the second level cache
723-
private boolean canClearEntityEntryReference() {
724-
EntityEntry entityEntry = managedEntity.$$_hibernate_getEntityEntry();
731+
private static boolean canClearEntityEntryReference(EntityEntry entityEntry) {
725732
return !(entityEntry instanceof ImmutableEntityEntry)
726-
|| !entityEntry.getPersister().canUseReferenceCacheEntries();
733+
|| !isReferenceCachingEnabled( entityEntry.getPersister() );
727734
}
728735

729736
@Override
@@ -735,6 +742,25 @@ private boolean canClearEntityEntryReference() {
735742
public void $$_hibernate_setInstanceId(int id) {
736743
managedEntity.$$_hibernate_setInstanceId( id );
737744
}
745+
746+
@Override
747+
public EntityEntry $$_hibernate_setPersistenceInfo(EntityEntry entityEntry, ManagedEntity previous, ManagedEntity next, int instanceId) {
748+
final EntityEntry oldEntry;
749+
if ( entityEntry == null ) {
750+
oldEntry = managedEntity.$$_hibernate_getEntityEntry();
751+
if ( canClearEntityEntryReference( oldEntry ) ) {
752+
managedEntity.$$_hibernate_setEntityEntry( null );
753+
}
754+
}
755+
else {
756+
managedEntity.$$_hibernate_setEntityEntry( entityEntry );
757+
oldEntry = null; // no need to retrieve the previous entity entry
758+
}
759+
this.previous = previous;
760+
this.next = next;
761+
managedEntity.$$_hibernate_setInstanceId( instanceId );
762+
return oldEntry;
763+
}
738764
}
739765

740766
/**

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,4 +128,26 @@ default ManagedEntity asManagedEntity() {
128128
return this;
129129
}
130130

131+
/**
132+
* Utility method that allows injecting all persistence-related information on the managed entity at once.
133+
*
134+
* @param entityEntry the {@link EntityEntry} associated with this entity instance
135+
* @param previous the previous entry
136+
* @param next the next entry
137+
* @param instanceId unique identifier for this instance
138+
* @return the previous {@link EntityEntry} contained in this managed entity, or {@code null}
139+
* @see #$$_hibernate_setEntityEntry(EntityEntry)
140+
* @see #$$_hibernate_setPreviousManagedEntity(ManagedEntity)
141+
* @see #$$_hibernate_setNextManagedEntity(ManagedEntity)
142+
* @see #$$_hibernate_setInstanceId(int)
143+
* @since 7.0
144+
*/
145+
default EntityEntry $$_hibernate_setPersistenceInfo(EntityEntry entityEntry, ManagedEntity previous, ManagedEntity next, int instanceId) {
146+
final EntityEntry oldEntry = $$_hibernate_getEntityEntry();
147+
$$_hibernate_setEntityEntry( entityEntry );
148+
$$_hibernate_setPreviousManagedEntity( previous );
149+
$$_hibernate_setNextManagedEntity( next );
150+
$$_hibernate_setInstanceId( instanceId );
151+
return oldEntry;
152+
}
131153
}

hibernate-core/src/main/java/org/hibernate/internal/util/collections/InstanceIdentityStore.java

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@
77
import org.checkerframework.checker.nullness.qual.Nullable;
88
import org.hibernate.engine.spi.InstanceIdentity;
99

10+
import java.util.ConcurrentModificationException;
11+
1012
/**
1113
* Utility collection that takes advantage of {@link InstanceIdentity}'s identifier to store objects.
1214
* The store is based on {@link AbstractPagedArray} and it stores element using their instance-id
1315
* as index.
1416
* <p>
17+
* Instance ids are considered to start from 1, and if two instances are found to have the
18+
* same identifier a {@link java.util.ConcurrentModificationException will be thrown}.
19+
* <p>
1520
* Both keys and values are stored in this array, requiring very few allocations to keep track of the pair.
1621
* The downside to this is we cannot easily access the key, especially if asking for a specific type, since
1722
* that would cause type-pollution issues at the call site that would degrade performance.
@@ -26,7 +31,7 @@ public class InstanceIdentityStore<V> extends AbstractPagedArray<Object> {
2631
* @return the index of the corresponding key instance in the array
2732
*/
2833
private static int toKeyIndex(int instanceId) {
29-
return instanceId * 2;
34+
return (instanceId - 1) * 2;
3035
}
3136

3237
/**
@@ -41,6 +46,10 @@ private static int toKeyIndex(int instanceId) {
4146
* equality check ({@code ==}) with the provided key to ensure it corresponds to the mapped one
4247
*/
4348
public @Nullable V get(int instanceId, Object key) {
49+
if ( instanceId <= 0 ) {
50+
return null;
51+
}
52+
4453
final int keyIndex = toKeyIndex( instanceId );
4554
final Page<Object> page = getPage( keyIndex );
4655
if ( page != null ) {
@@ -50,6 +59,12 @@ private static int toKeyIndex(int instanceId) {
5059
//noinspection unchecked
5160
return (V) page.get( offset + 1 );
5261
}
62+
else {
63+
throw new ConcurrentModificationException(
64+
"Found a different instance corresponding to instanceId [" + instanceId +
65+
"], this might indicate a concurrent access to this persistence context."
66+
);
67+
}
5368
}
5469
return null;
5570
}
@@ -61,12 +76,14 @@ private static int toKeyIndex(int instanceId) {
6176
* @param key key with which the specified value is to be associated
6277
* @param value value to be associated with the specified key
6378
*/
64-
public <K extends InstanceIdentity> void put(K key, V value) {
79+
public void put(Object key, int instanceId, V value) {
6580
if ( key == null ) {
6681
throw new NullPointerException( "This store does not support null keys" );
6782
}
83+
else if ( instanceId <= 0 ) {
84+
throw new IllegalArgumentException( "Instance ID must be a positive value" );
85+
}
6886

69-
final int instanceId = key.$$_hibernate_getInstanceId();
7087
final int keyIndex = toKeyIndex( instanceId );
7188
final Page<Object> page = getOrCreateEntryPage( keyIndex );
7289
final int pageOffset = toPageOffset( keyIndex );
@@ -83,6 +100,10 @@ public <K extends InstanceIdentity> void put(K key, V value) {
83100
* equality check ({@code ==}) with the provided key to ensure it corresponds to the mapped one
84101
*/
85102
public void remove(int instanceId, Object key) {
103+
if ( instanceId <= 0 ) {
104+
return;
105+
}
106+
86107
final int keyIndex = toKeyIndex( instanceId );
87108
final Page<Object> page = getPage( keyIndex );
88109
if ( page != null ) {
@@ -93,8 +114,10 @@ public void remove(int instanceId, Object key) {
93114
page.set( pageOffset + 1, null );
94115
}
95116
else {
96-
// If it doesn't, reset the array value to the old key
97-
page.set( pageOffset, k );
117+
throw new ConcurrentModificationException(
118+
"Found a different instance corresponding to instanceId [" + instanceId +
119+
"], this might indicate a concurrent access to this persistence context."
120+
);
98121
}
99122
}
100123
}

0 commit comments

Comments
 (0)