From 6e4bee8c570103de82dc04aabd98da34ce61cd00 Mon Sep 17 00:00:00 2001 From: Andrea Boriero Date: Wed, 22 Feb 2023 21:01:33 +0100 Subject: [PATCH] HHH-16197 Circular references of the same entity result in different Java objects when caching is enabled and using a query --- .../spi/SharedSessionContractImplementor.java | 10 +++++ .../internal/DefaultPersistEventListener.java | 6 +-- .../org/hibernate/internal/SessionImpl.java | 19 +++++++++ .../entity/AbstractEntityInitializer.java | 42 +++++++++++++++++-- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java index 418f93407a..a3289c8063 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionContractImplementor.java @@ -329,6 +329,16 @@ public interface SharedSessionContractImplementor */ 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, * which is not involved in an association, using only the diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java index f51530af60..b2134d3886 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultPersistEventListener.java @@ -81,7 +81,7 @@ public class DefaultPersistEventListener private void persist(PersistEvent event, PersistContext createCache, Object entity) { final EventSource source = event.getSession(); 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 ) ) { case DETACHED: throw new PersistentObjectException( "detached entity passed to persist: " @@ -133,13 +133,13 @@ public class DefaultPersistEventListener return entityState; } - private static String entityName(PersistEvent event, Object entity) { + private static String entityName(PersistEvent event, Object entity, EntityEntry entityEntry) { if ( event.getEntityName() != null ) { return event.getEntityName(); } else { // changes event.entityName by side effect! - final String entityName = event.getSession().bestGuessEntityName( entity ); + final String entityName = event.getSession().bestGuessEntityName( entity, entityEntry ); event.setEntityName( entityName ); return entityName; } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 0a514eb832..f9bcd3b7b6 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -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 public String getEntityName(Object object) { checkOpen(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java index 34bf5c01cd..171439fa0f 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/graph/entity/AbstractEntityInitializer.java @@ -699,9 +699,9 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces preLoad( rowProcessingState ); final LazyInitializer lazyInitializer = extractLazyInitializer( entityInstance ); + final SharedSessionContractImplementor session = rowProcessingState.getSession(); + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); if ( lazyInitializer != null ) { - final SharedSessionContractImplementor session = rowProcessingState.getSession(); - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); Object instance = persistenceContext.getEntity( entityKey ); if ( instance == null ) { instance = resolveInstance( @@ -715,8 +715,42 @@ public abstract class AbstractEntityInitializer extends AbstractFetchParentAcces entityInstanceForNotify = instance; } else { - initializeEntity( entityInstance, rowProcessingState ); - entityInstanceForNotify = entityInstance; + if ( entityDescriptor.canReadFromCache() ) { + /* + @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 );