HHH-16197 Circular references of the same entity result in different Java objects when caching is enabled and using a query

This commit is contained in:
Andrea Boriero 2023-02-22 21:01:33 +01:00 committed by Andrea Boriero
parent c5769ad06e
commit 6e4bee8c57
4 changed files with 70 additions and 7 deletions

View File

@ -329,6 +329,16 @@ public interface SharedSessionContractImplementor
*/ */
String bestGuessEntityName(Object object); String bestGuessEntityName(Object object);
/**
* Obtain the best estimate of the entity name of the given entity
* instance, which is not involved in an association, by also
* considering information held in the proxy, and whether the object
* is already associated with this session.
*/
default String bestGuessEntityName(Object object, EntityEntry entry) {
return bestGuessEntityName( object );
}
/** /**
* Obtain an estimate of the entity name of the given entity instance, * Obtain an estimate of the entity name of the given entity instance,
* which is not involved in an association, using only the * which is not involved in an association, using only the

View File

@ -81,7 +81,7 @@ public class DefaultPersistEventListener
private void persist(PersistEvent event, PersistContext createCache, Object entity) { private void persist(PersistEvent event, PersistContext createCache, Object entity) {
final EventSource source = event.getSession(); final EventSource source = event.getSession();
final EntityEntry entityEntry = source.getPersistenceContextInternal().getEntry( entity ); final EntityEntry entityEntry = source.getPersistenceContextInternal().getEntry( entity );
final String entityName = entityName( event, entity ); final String entityName = entityName( event, entity, entityEntry );
switch ( entityState( event, entity, entityName, entityEntry ) ) { switch ( entityState( event, entity, entityName, entityEntry ) ) {
case DETACHED: case DETACHED:
throw new PersistentObjectException( "detached entity passed to persist: " throw new PersistentObjectException( "detached entity passed to persist: "
@ -133,13 +133,13 @@ public class DefaultPersistEventListener
return entityState; return entityState;
} }
private static String entityName(PersistEvent event, Object entity) { private static String entityName(PersistEvent event, Object entity, EntityEntry entityEntry) {
if ( event.getEntityName() != null ) { if ( event.getEntityName() != null ) {
return event.getEntityName(); return event.getEntityName();
} }
else { else {
// changes event.entityName by side effect! // changes event.entityName by side effect!
final String entityName = event.getSession().bestGuessEntityName( entity ); final String entityName = event.getSession().bestGuessEntityName( entity, entityEntry );
event.setEntityName( entityName ); event.setEntityName( entityName );
return entityName; return entityName;
} }

View File

@ -1718,6 +1718,25 @@ public class SessionImpl
} }
} }
@Override
public String bestGuessEntityName(Object object, EntityEntry entry) {
final LazyInitializer lazyInitializer = extractLazyInitializer( object );
if ( lazyInitializer != null ) {
// it is possible for this method to be called during flush processing,
// so make certain that we do not accidentally initialize an uninitialized proxy
if ( lazyInitializer.isUninitialized() ) {
return lazyInitializer.getEntityName();
}
object = lazyInitializer.getImplementation();
}
if ( entry == null ) {
return guessEntityName( object );
}
else {
return entry.getPersister().getEntityName();
}
}
@Override @Override
public String getEntityName(Object object) { public String getEntityName(Object object) {
checkOpen(); checkOpen();

View File

@ -699,9 +699,9 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
preLoad( rowProcessingState ); preLoad( rowProcessingState );
final LazyInitializer lazyInitializer = extractLazyInitializer( entityInstance ); final LazyInitializer lazyInitializer = extractLazyInitializer( entityInstance );
final SharedSessionContractImplementor session = rowProcessingState.getSession();
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
if ( lazyInitializer != null ) { if ( lazyInitializer != null ) {
final SharedSessionContractImplementor session = rowProcessingState.getSession();
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
Object instance = persistenceContext.getEntity( entityKey ); Object instance = persistenceContext.getEntity( entityKey );
if ( instance == null ) { if ( instance == null ) {
instance = resolveInstance( instance = resolveInstance(
@ -715,8 +715,42 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces
entityInstanceForNotify = instance; entityInstanceForNotify = instance;
} }
else { else {
initializeEntity( entityInstance, rowProcessingState ); if ( entityDescriptor.canReadFromCache() ) {
entityInstanceForNotify = entityInstance; /*
@Cache
class Child {
@ManyToOne
private Parent parent;
}
@Cache
class Parent {
@OneToOne
private Parent parent;
}
when the query "select c from Child c" is executed and the second level cache (2LC) contains
an instance of Child and Parent
then when the EntitySelectFetchInitializer#initializeInstance() is executed before the EntityResultInitializer one
the persistence context will contain the instances retrieved form the 2LC
*/
final Object entity = persistenceContext.getEntity( entityKey );
if ( entity != null ) {
entityInstance = entity;
registerLoadingEntity( rowProcessingState, entityInstance );
initializeEntityInstance( entityInstance, rowProcessingState );
}
else {
initializeEntity( entityInstance, rowProcessingState );
}
entityInstanceForNotify = entityInstance;
}
else {
initializeEntity( entityInstance, rowProcessingState );
entityInstanceForNotify = entityInstance;
}
} }
notifyResolutionListeners( entityInstanceForNotify ); notifyResolutionListeners( entityInstanceForNotify );