From d7f4ebe2d563dabb327a471a0067ea121ed7fd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jun 2025 10:49:54 +0200 Subject: [PATCH 1/3] HHH-19532 Add unwrap() method to StatelessSession/SharedSessionContract --- .../src/main/java/org/hibernate/Session.java | 5 ++++ .../org/hibernate/SharedSessionContract.java | 25 +++++++++++++++++++ .../org/hibernate/internal/SessionImpl.java | 3 ++- .../internal/StatelessSessionImpl.java | 13 ++++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/Session.java b/hibernate-core/src/main/java/org/hibernate/Session.java index cd5c02d0f6ab..575e46b2f6b2 100644 --- a/hibernate-core/src/main/java/org/hibernate/Session.java +++ b/hibernate-core/src/main/java/org/hibernate/Session.java @@ -1494,4 +1494,9 @@ public interface Session extends SharedSessionContract, EntityManager { */ @Override @Deprecated(since = "6.0") @SuppressWarnings("rawtypes") Query createQuery(CriteriaUpdate updateQuery); + + @Override + default T unwrap(Class type) { + return SharedSessionContract.super.unwrap(type); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java index 89dd21243404..9c1b0ef123c7 100644 --- a/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/SharedSessionContract.java @@ -10,6 +10,7 @@ import java.util.function.Function; import jakarta.persistence.EntityGraph; +import jakarta.persistence.PersistenceException; import org.hibernate.graph.RootGraph; import org.hibernate.jdbc.ReturningWork; import org.hibernate.jdbc.Work; @@ -472,4 +473,28 @@ default R fromTransaction(Function action) { final Transaction transaction = beginTransaction(); return manageTransaction( transaction, transaction, action ); } + + /** + * Return an object of the specified type to allow access to + * a provider-specific API. + * + * @param type the class of the object to be returned. + * This is usually either the underlying class + * implementing {@code SharedSessionContract} or an + * interface it implements. + * @return an instance of the specified class + * @throws PersistenceException if the provider does not + * support the given type + */ + default T unwrap(Class type) { + // Not checking type.isInstance(...) because some implementations + // might want to hide that they implement some types. + // Implementations wanting a more liberal behavior need to override this method. + if ( type.isAssignableFrom( SharedSessionContract.class ) ) { + return type.cast( this ); + } + + throw new PersistenceException( + "Hibernate cannot unwrap '" + getClass().getName() + "' as '" + type.getName() + "'" ); + } } 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 02f25916ebf9..ed6c54b0b67d 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -2948,7 +2948,8 @@ public T unwrap(Class type) { return type.cast( persistenceContext ); } - throw new PersistenceException( "Hibernate cannot unwrap EntityManager as '" + type.getName() + "'" ); + throw new PersistenceException( + "Hibernate cannot unwrap '" + getClass().getName() + "' as '" + type.getName() + "'" ); } @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 5369ac021434..b4725a1968b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -9,6 +9,7 @@ import java.util.Set; import java.util.function.BiConsumer; +import jakarta.persistence.PersistenceException; import org.hibernate.AssertionFailure; import org.hibernate.FlushMode; import org.hibernate.HibernateException; @@ -1427,6 +1428,18 @@ public Object loadFromSecondLevelCache(EntityPersister persister, EntityKey enti return CacheLoadHelper.loadFromSecondLevelCache( this, instanceToLoad, lockMode, persister, entityKey ); } + @Override + public T unwrap(Class type) { + checkOpen(); + + if ( type.isInstance( this ) ) { + return type.cast( this ); + } + + throw new PersistenceException( + "Hibernate cannot unwrap '" + getClass().getName() + "' as '" + type.getName() + "'" ); + } + private static final class MultiLoadOptions implements MultiIdLoadOptions { private final LockOptions lockOptions; From b44a4047e933c20d717ce389efea2b5df59b95e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jun 2025 10:50:26 +0200 Subject: [PATCH 2/3] HHH-19531 Test use of session proxies with SelectionSpecification/MutationSpecification --- .../SimpleQuerySpecificationTests.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/query/dynamic/SimpleQuerySpecificationTests.java b/hibernate-core/src/test/java/org/hibernate/orm/test/query/dynamic/SimpleQuerySpecificationTests.java index 25803ba3f949..3d452cdc0475 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/query/dynamic/SimpleQuerySpecificationTests.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/query/dynamic/SimpleQuerySpecificationTests.java @@ -8,6 +8,8 @@ import jakarta.persistence.criteria.CriteriaQuery; import org.hibernate.SessionFactory; +import org.hibernate.engine.spi.SessionLazyDelegator; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.query.IllegalMutationQueryException; import org.hibernate.query.IllegalSelectQueryException; import org.hibernate.query.Order; @@ -19,6 +21,7 @@ import org.hibernate.query.restriction.Restriction; import org.hibernate.testing.jdbc.SQLStatementInspector; import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.junit.jupiter.api.Test; @@ -160,6 +163,46 @@ void testSimpleMutationRestriction(SessionFactoryScope factoryScope) { assertThat( sqlCollector.getSqlQueries().get( 0 ) ).contains( " where be1_0.position between ? and ?" ); } + @Test + @JiraKey("HHH-19531") + void testSelectionOnSessionProxy(SessionFactoryScope factoryScope) { + final SQLStatementInspector sqlCollector = factoryScope.getCollectingStatementInspector(); + + factoryScope.inTransaction( (session) -> { + var sessionProxy = new SessionLazyDelegator( () -> session ); + // The test only makes sense if this is true. It currently is, but who knows what the future has in store for us. + //noinspection ConstantValue + assert !(sessionProxy instanceof SharedSessionContractImplementor); + + sqlCollector.clear(); + SelectionSpecification.create( BasicEntity.class, "from BasicEntity" ) + .createQuery( sessionProxy ) + .list(); + } ); + + assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); + } + + @Test + @JiraKey("HHH-19531") + void testMutationOnSessionProxy(SessionFactoryScope factoryScope) { + final SQLStatementInspector sqlCollector = factoryScope.getCollectingStatementInspector(); + + factoryScope.inTransaction( (session) -> { + var sessionProxy = new SessionLazyDelegator( () -> session ); + // The test only makes sense if this is true. It currently is, but who knows what the future has in store for us. + //noinspection ConstantValue + assert !(sessionProxy instanceof SharedSessionContractImplementor); + + sqlCollector.clear(); + MutationSpecification.create( BasicEntity.class, "delete BasicEntity" ) + .createQuery( sessionProxy ) + .executeUpdate(); + } ); + + assertThat( sqlCollector.getSqlQueries() ).hasSize( 1 ); + } + @Test void testSimpleMutationRestrictionAsReference(SessionFactoryScope factoryScope) { final SQLStatementInspector sqlCollector = factoryScope.getCollectingStatementInspector(); From 03145657068385a0901630a110685c730dcc50ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoann=20Rodi=C3=A8re?= Date: Thu, 12 Jun 2025 10:54:00 +0200 Subject: [PATCH 3/3] HHH-19531 Prefer unwrap() to casting sessions in SelectionSpecification/MutationSpecification Because casts can fail on session proxies, e.g. SessionLazyDelegator, or Spring's session proxies, or Quarkus'. --- .../query/specification/internal/MutationSpecificationImpl.java | 2 +- .../specification/internal/SelectionSpecificationImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/MutationSpecificationImpl.java b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/MutationSpecificationImpl.java index 47331c7f3200..4ea754a98b9f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/MutationSpecificationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/MutationSpecificationImpl.java @@ -122,7 +122,7 @@ public MutationQuery createQuery(StatelessSession session) { } public MutationQuery createQuery(SharedSessionContract session) { - final var sessionImpl = (SharedSessionContractImplementor) session; + final var sessionImpl = session.unwrap(SharedSessionContractImplementor.class); final SqmDeleteOrUpdateStatement sqmStatement = build( sessionImpl.getFactory().getQueryEngine() ); return new QuerySqmImpl<>( sqmStatement, true, null, sessionImpl ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java index 5cdd424dc8ea..6a4e3db5b132 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/specification/internal/SelectionSpecificationImpl.java @@ -164,7 +164,7 @@ public SelectionQuery createQuery(StatelessSession session) { } public SelectionQuery createQuery(SharedSessionContract session) { - final var sessionImpl = (SharedSessionContractImplementor) session; + final var sessionImpl = session.unwrap(SharedSessionContractImplementor.class); final SqmSelectStatement sqmStatement = build( sessionImpl.getFactory().getQueryEngine() ); return new SqmSelectionQueryImpl<>( sqmStatement, true, resultType, sessionImpl ); }