From 5a3b7bd4d1dde37a8beb7ff6cad1b558cf05c0d0 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 18 Nov 2025 12:44:34 +0100 Subject: [PATCH 1/3] simplify Session.contains() I have no clue what the call to delayedAfterCompletion() was doing here, but I don't see how it could possibly be correct. --- .../org/hibernate/internal/SessionImpl.java | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) 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 70174fe63dd5..a2a6bb291227 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1634,6 +1634,7 @@ public boolean contains(Object object) { pulseTransactionCoordinator(); if ( object == null ) { + //TODO: this should throw IllegalArgumentException return false; } @@ -1662,25 +1663,10 @@ public boolean contains(Object object) { // an entry in the session's persistence context and the entry reports // that the entity has not been removed final var entry = persistenceContext.getEntry( object ); - delayedAfterCompletion(); - if ( entry == null ) { - if ( lazyInitializer == null && persistenceContext.getEntry( object ) == null ) { - // check if it is even an entity -> if not throw an exception (per JPA) - try { - final String entityName = getEntityNameResolver().resolveEntityName( object ); - if ( entityName == null ) { - throw new IllegalArgumentException( "Could not resolve entity name for class '" - + object.getClass() + "'" ); - } - else { - requireEntityPersister( entityName ); - } - } - catch ( HibernateException e ) { - throw new IllegalArgumentException( "Class '" + object.getClass() - + "' is not an entity class", e ); - } + if ( lazyInitializer == null ) { + // if not an entity, throw an exception, as required by spec + assertInstanceOfEntityType( object ); } return false; } @@ -1696,6 +1682,23 @@ public boolean contains(Object object) { } } + private void assertInstanceOfEntityType(Object object) { + try { + final String entityName = getEntityNameResolver().resolveEntityName( object ); + if ( entityName == null ) { + throw new IllegalArgumentException( "Could not resolve entity name for class '" + + object.getClass() + "'" ); + } + else { + requireEntityPersister( entityName ); + } + } + catch ( HibernateException e ) { + throw new IllegalArgumentException( "Class '" + object.getClass() + + "' is not an entity class", e ); + } + } + @Override public boolean contains(String entityName, Object object) { checkOpenOrWaitingForAutoClose(); @@ -1739,7 +1742,6 @@ public boolean contains(String entityName, Object object) { // an entry in the session's persistence context and the entry reports // that the entity has not been removed final var entry = persistenceContext.getEntry( object ); - delayedAfterCompletion(); return entry != null && !entry.getStatus().isDeletedOrGone(); } catch ( MappingException e ) { From 40e1dfdb2fe7c9bab38e6b790174d2a75d02d94f Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 18 Nov 2025 16:30:56 +0100 Subject: [PATCH 2/3] put back call to delayedAfterCompletion() --- .../src/main/java/org/hibernate/internal/SessionImpl.java | 2 ++ 1 file changed, 2 insertions(+) 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 a2a6bb291227..f16057aac7f4 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1632,6 +1632,7 @@ else if ( isPersistentAttributeInterceptable( object ) ) { public boolean contains(Object object) { checkOpen(); pulseTransactionCoordinator(); + delayedAfterCompletion(); if ( object == null ) { //TODO: this should throw IllegalArgumentException @@ -1703,6 +1704,7 @@ private void assertInstanceOfEntityType(Object object) { public boolean contains(String entityName, Object object) { checkOpenOrWaitingForAutoClose(); pulseTransactionCoordinator(); + delayedAfterCompletion(); if ( object == null ) { return false; From d443935a2fe250fafd1d578bf1fcab0bab3d76bf Mon Sep 17 00:00:00 2001 From: Gavin King Date: Tue, 18 Nov 2025 17:58:29 +0100 Subject: [PATCH 3/3] HHH-19941 deprecate Session.contains(String, Object) and introduce SharedSessionContractImplementor.isManaged() --- .../src/main/java/org/hibernate/Session.java | 3 + .../engine/spi/SessionDelegatorBaseImpl.java | 5 + .../spi/SharedSessionContractImplementor.java | 9 ++ .../spi/SharedSessionDelegatorBaseImpl.java | 5 + .../internal/DefaultRefreshEventListener.java | 7 +- .../id/IdentifierGeneratorHelper.java | 10 +- .../org/hibernate/internal/SessionImpl.java | 91 ++++++++----------- .../internal/StatelessSessionImpl.java | 5 + 8 files changed, 69 insertions(+), 66 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index 945aa0a6cbd5..cd4109bc9a24 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -470,7 +470,10 @@ public interface Session extends SharedSessionContract, EntityManager { * @param object an instance of a persistent class * * @return {@code true} if the given instance is associated with this {@code Session} + * + * @deprecated Use {@link #contains(Object)} instead. */ + @Deprecated(since = "7.2", forRemoval = true) boolean contains(String entityName, Object object); /** diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java index 81a626587312..abcfa37af833 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionDelegatorBaseImpl.java @@ -800,6 +800,11 @@ public boolean contains(Object object) { return delegate.contains( object ); } + @Override + public boolean isManaged(Object entity) { + return delegate.isManaged( entity ); + } + @Override public LockModeType getLockMode(Object entity) { return delegate.getLockMode( entity ); 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 b441357630f2..958e71319ab4 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 @@ -503,6 +503,15 @@ default Integer getConfiguredJdbcBatchSize() { */ PersistenceContext getPersistenceContextInternal(); + /** + * Is the given entity managed by this session? + * + * @return true if this is a stateful session and + * the entity belongs to its persistence + * context and was not removed + */ + boolean isManaged(Object entity); + /** * detect in-memory changes, determine if the changes are to tables * named in the query and, if so, complete execution the flush diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionDelegatorBaseImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionDelegatorBaseImpl.java index 54930d404641..4f7f1951c05f 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionDelegatorBaseImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SharedSessionDelegatorBaseImpl.java @@ -742,4 +742,9 @@ public SharedSessionBuilder sessionWithOptions() { public TransactionCompletionCallbacksImplementor getTransactionCompletionCallbacksImplementor() { return delegate.getTransactionCompletionCallbacksImplementor(); } + + @Override + public boolean isManaged(Object entity) { + return delegate.isManaged( entity ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java index 0658a0899e82..d9b7f0e54d50 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultRefreshEventListener.java @@ -73,7 +73,7 @@ private static void handleUninitializedProxy( EventSource source, Object object, PersistenceContext persistenceContext) { - final boolean isTransient = isTransient( event, source, object ); + final boolean isTransient = !source.isManaged( object ); // If refreshAlready is nonempty then the refresh is the result of a cascade refresh and the // refresh of the parent will take care of initializing the lazy entity and setting the // correct lock. This is needed only when the refresh is called directly on a lazy entity. @@ -116,11 +116,6 @@ else if ( isTransient ) { } } - private static boolean isTransient(RefreshEvent event, EventSource source, Object object) { - final String entityName = event.getEntityName(); - return entityName == null ? !source.contains( object ) : !source.contains( entityName, object ); - } - private static void refresh(RefreshEvent event, RefreshContext refreshedAlready, Object object) { final var source = event.getSession(); final var persistenceContext = source.getPersistenceContextInternal(); diff --git a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGeneratorHelper.java b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGeneratorHelper.java index d0c4d2faa9b1..7f564f401ca7 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/IdentifierGeneratorHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/id/IdentifierGeneratorHelper.java @@ -20,7 +20,6 @@ import org.hibernate.TransientObjectException; import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.engine.config.spi.ConfigurationService; -import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.id.enhanced.ImplicitDatabaseObjectNamingStrategy; import org.hibernate.id.enhanced.StandardNamingStrategy; @@ -82,16 +81,15 @@ else if ( integralType == BigDecimal.class ) { public static Object getForeignId( String entityName, String propertyName, SharedSessionContractImplementor sessionImplementor, Object object) { - final var persister = - sessionImplementor.getFactory().getMappingMetamodel() - .getEntityDescriptor( entityName ); - if ( sessionImplementor instanceof SessionImplementor statefulSession - && statefulSession.contains( entityName, object ) ) { + if ( sessionImplementor.isManaged( object ) ) { //abort the save (the object is already saved by a circular cascade) return SHORT_CIRCUIT_INDICATOR; //throw new IdentifierGenerationException("save associated object first, or disable cascade for inverse association"); } else { + final var persister = + sessionImplementor.getFactory().getMappingMetamodel() + .getEntityDescriptor( entityName ); return identifier( sessionImplementor, entityType( propertyName, persister ), associatedEntity( entityName, propertyName, object, persister ) ); } 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 f16057aac7f4..08486989087a 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1338,13 +1338,45 @@ private void fireRefresh(final RefreshContext refreshedAlready, final RefreshEve } private void checkEntityManaged(String entityName, Object entity) { - if ( !managed( entityName, entity ) ) { + if ( !isManaged( entity ) ) { throw new IllegalArgumentException( "Given entity is not associated with the persistence context" ); } } - private boolean managed(String entityName, Object entity) { - return entityName == null ? contains( entity ) : contains( entityName, entity ); + @Override + public boolean isManaged(Object entity) { + try { + final var lazyInitializer = extractLazyInitializer( entity ); + if ( lazyInitializer != null ) { + //do not use proxiesByKey, since not all + //proxies that point to this session's + //instances are in that collection! + if ( lazyInitializer.isUninitialized() ) { + //if it is an uninitialized proxy, pointing + //with this session, then when it is accessed, + //the underlying instance will be "contained" + return lazyInitializer.getSession() == this; + } + else { + //if it is initialized, see if the underlying + //instance is contained, since we need to + //account for the fact that it might have been + //evicted + entity = lazyInitializer.getImplementation(); + } + } + // A session is considered to contain an entity only if the entity has + // an entry in the session's persistence context and the entry reports + // that the entity has not been removed + final var entry = persistenceContext.getEntry( entity ); + return entry != null && !entry.getStatus().isDeletedOrGone(); + } + catch ( MappingException e ) { + throw new IllegalArgumentException( e.getMessage(), e ); + } + catch ( RuntimeException e ) { + throw getExceptionConverter().convert( e ); + } } // replicate() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1700,58 +1732,9 @@ private void assertInstanceOfEntityType(Object object) { } } - @Override + @Override @Deprecated(forRemoval = true) public boolean contains(String entityName, Object object) { - checkOpenOrWaitingForAutoClose(); - pulseTransactionCoordinator(); - delayedAfterCompletion(); - - if ( object == null ) { - return false; - } - - try { - final var lazyInitializer = extractLazyInitializer( object ); - if ( lazyInitializer == null && persistenceContext.getEntry( object ) == null ) { - // check if it is an entity -> if not throw an exception (per JPA) - try { - requireEntityPersister( entityName ); - } - catch (HibernateException e) { - throw new IllegalArgumentException( "Not an entity [" + entityName + "] : " + object ); - } - } - - if ( lazyInitializer != null ) { - //do not use proxiesByKey, since not all - //proxies that point to this session's - //instances are in that collection! - if ( lazyInitializer.isUninitialized() ) { - //if it is an uninitialized proxy, pointing - //with this session, then when it is accessed, - //the underlying instance will be "contained" - return lazyInitializer.getSession() == this; - } - else { - //if it is initialized, see if the underlying - //instance is contained, since we need to - //account for the fact that it might have been - //evicted - object = lazyInitializer.getImplementation(); - } - } - // A session is considered to contain an entity only if the entity has - // an entry in the session's persistence context and the entry reports - // that the entity has not been removed - final var entry = persistenceContext.getEntry( object ); - return entry != null && !entry.getStatus().isDeletedOrGone(); - } - catch ( MappingException e ) { - throw new IllegalArgumentException( e.getMessage(), e ); - } - catch ( RuntimeException e ) { - throw getExceptionConverter().convert( e ); - } + return contains( object ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java index 3c9130dc6fd2..7129a1bcd469 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -1302,6 +1302,11 @@ public boolean isIdentifierRollbackEnabled() { return false; } + @Override + public boolean isManaged(Object entity) { + return false; + } + ///////////////////////////////////////////////////////////////////////////////////////////////////// //TODO: COPY/PASTE FROM SessionImpl, pull up!