diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java index f942818f85be..47473a4c6cc6 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java @@ -41,7 +41,7 @@ import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.spi.SQLExceptionConversionDelegate; import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtractor; @@ -797,7 +797,7 @@ private ReadUncommittedLockingStrategy(EntityPersister lockable, LockMode lockMo } @Override - public void lock(Object id, Object version, Object object, int timeout, EventSource session) + public void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) throws StaleObjectStateException, JDBCException { if ( getLockMode().greaterThan( LockMode.READ ) ) { LOG.hsqldbSupportsOnlyReadCommittedIsolation(); diff --git a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java index 15d2bf11cb74..cd32aa8e401d 100644 --- a/hibernate-core/src/main/java/org/hibernate/StatelessSession.java +++ b/hibernate-core/src/main/java/org/hibernate/StatelessSession.java @@ -355,6 +355,23 @@ public interface StatelessSession extends SharedSessionContract { */ List getMultiple(Class entityClass, List ids); + /** + * Retrieve multiple rows, obtaining the specified lock mode, + * and returning entity instances in a list where the position + * of an instance in the list matches the position of its + * identifier in the given array, and the list contains a null + * value if there is no persistent instance matching a given + * identifier. + * + * @param entityClass The class of the entity to retrieve + * @param ids The ids of the entities to retrieve + * @param lockMode The lock mode to apply to the entities + * @return an ordered list of detached entity instances, with + * null elements representing missing entities + * @since 7.0 + */ + List getMultiple(Class entityClass, List ids, LockMode lockMode); + /** * Refresh the entity instance state from the database. * diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java index 7780e037ddb9..4fd626676fc1 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SpannerDialect.java @@ -25,7 +25,7 @@ import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.engine.jdbc.env.spi.SchemaNameResolver; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.mapping.Column; import org.hibernate.mapping.ForeignKey; @@ -891,7 +891,7 @@ static class DoNothingLockingStrategy implements LockingStrategy { @Override public void lock( - Object id, Object version, Object object, int timeout, EventSource session) + Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) throws StaleObjectStateException, LockingStrategyException { // Do nothing. Cloud Spanner doesn't have have locking strategies. } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java index b3ba6f14ffb4..a0dada0cfc5f 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/AbstractSelectLockingStrategy.java @@ -11,7 +11,7 @@ import org.hibernate.StaleObjectStateException; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.sql.SimpleSelect; import org.hibernate.stat.spi.StatisticsImplementor; @@ -69,7 +69,7 @@ protected String generateLockString(int lockTimeout) { } @Override - public void lock(Object id, Object version, Object object, int timeout, EventSource session) + public void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) throws StaleObjectStateException, JDBCException { final String sql = determineSql( timeout ); final SessionFactoryImplementor factory = session.getFactory(); @@ -112,7 +112,7 @@ public void lock(Object id, Object version, Object object, int timeout, EventSou } } - private JDBCException jdbcException(Object id, EventSource session, SQLException sqle, String sql) { + private JDBCException jdbcException(Object id, SharedSessionContractImplementor session, SQLException sqle, String sql) { return session.getJdbcServices().getSqlExceptionHelper() .convert( sqle, "could not lock: " + infoString( lockable, id, session.getFactory() ), sql ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/LockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/LockingStrategy.java index 7a27a2525020..afa2e49dcd9a 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/LockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/LockingStrategy.java @@ -5,6 +5,7 @@ package org.hibernate.dialect.lock; import org.hibernate.StaleObjectStateException; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.event.spi.EventSource; /** @@ -22,6 +23,28 @@ * @author Steve Ebersole */ public interface LockingStrategy { + /** + * Acquire an appropriate type of lock on the underlying data that will + * endure until the end of the current transaction. + * + * @param id The id of the row to be locked + * @param version The current version (or null if not versioned) + * @param object The object logically being locked (currently not used) + * @param timeout timeout in milliseconds, 0 = no wait, -1 = wait indefinitely + * @param session The session from which the lock request originated + * + * @throws StaleObjectStateException Indicates an inability to locate the database row as part of acquiring + * the requested lock. + * @throws LockingStrategyException Indicates a failure in the lock attempt + * + * @deprecated Use {@link #lock(Object, Object, Object, int, SharedSessionContractImplementor)} + */ + @Deprecated(since = "7") + default void lock(Object id, Object version, Object object, int timeout, EventSource session) + throws StaleObjectStateException, LockingStrategyException { + lock( id, version, object, timeout, (SharedSessionContractImplementor) session ); + } + /** * Acquire an appropriate type of lock on the underlying data that will * endure until the end of the current transaction. @@ -36,6 +59,13 @@ public interface LockingStrategy { * the requested lock. * @throws LockingStrategyException Indicates a failure in the lock attempt */ - void lock(Object id, Object version, Object object, int timeout, EventSource session) - throws StaleObjectStateException, LockingStrategyException; + default void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) + throws StaleObjectStateException, LockingStrategyException { + if ( session instanceof EventSource eventSource ) { + lock( id, version, object, timeout, eventSource ); + } + else { + throw new UnsupportedOperationException( "Optimistic locking strategies not supported in stateless session" ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java index bfa087169f7b..108b6633fab7 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticForceIncrementLockingStrategy.java @@ -7,7 +7,7 @@ import org.hibernate.HibernateException; import org.hibernate.LockMode; import org.hibernate.engine.spi.EntityEntry; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.persister.entity.EntityPersister; /** @@ -39,7 +39,7 @@ public PessimisticForceIncrementLockingStrategy(EntityPersister lockable, LockMo } @Override - public void lock(Object id, Object version, Object object, int timeout, EventSource session) { + public void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) { if ( !lockable.isVersioned() ) { throw new HibernateException( "[" + lockMode + "] not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java index 25459de8667a..10b2fc4d9df8 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticReadUpdateLockingStrategy.java @@ -14,7 +14,7 @@ import org.hibernate.StaleObjectStateException; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; @@ -69,7 +69,7 @@ public PessimisticReadUpdateLockingStrategy(EntityPersister lockable, LockMode l } @Override - public void lock(Object id, Object version, Object object, int timeout, EventSource session) { + public void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) { if ( !lockable.isVersioned() ) { throw new HibernateException( "write locks via update not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java index a96eda04de80..67e96c0b62bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/PessimisticWriteUpdateLockingStrategy.java @@ -14,7 +14,7 @@ import org.hibernate.StaleObjectStateException; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; @@ -68,7 +68,7 @@ public PessimisticWriteUpdateLockingStrategy(EntityPersister lockable, LockMode } @Override - public void lock(Object id, Object version, Object object, int timeout, EventSource session) { + public void lock(Object id, Object version, Object object, int timeout, SharedSessionContractImplementor session) { if ( !lockable.isVersioned() ) { throw new HibernateException( "write locks via update not supported for non-versioned entities [" + lockable.getEntityName() + "]" ); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java b/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java index b8d330410593..052f9dc06245 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/lock/UpdateLockingStrategy.java @@ -14,7 +14,7 @@ import org.hibernate.StaleObjectStateException; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.pretty.MessageHelper; @@ -72,7 +72,7 @@ public void lock( Object version, Object object, int timeout, - EventSource session) throws StaleObjectStateException, JDBCException { + SharedSessionContractImplementor session) throws StaleObjectStateException, JDBCException { final String lockableEntityName = lockable.getEntityName(); if ( !lockable.isVersioned() ) { throw new HibernateException( "write locks via update not supported for non-versioned entities [" + lockableEntityName + "]" ); 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 074242e84e1b..fe44d888a12a 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 @@ -1236,4 +1236,9 @@ public FormatMapper getJsonFormatMapper() { public FormatMapper getXmlFormatMapper() { return delegate.getXmlFormatMapper(); } + + @Override + public Object loadFromSecondLevelCache(EntityPersister persister, EntityKey entityKey, Object instanceToLoad, LockMode lockMode) { + return delegate.loadFromSecondLevelCache( persister, entityKey, instanceToLoad, lockMode ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java index 3688c3931d12..7c64b09c2050 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionImplementor.java @@ -7,7 +7,6 @@ import jakarta.persistence.ConnectionConsumer; import jakarta.persistence.ConnectionFunction; import org.hibernate.HibernateException; -import org.hibernate.LockOptions; import org.hibernate.Session; import org.hibernate.engine.jdbc.LobCreationContext; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; @@ -95,11 +94,6 @@ default SessionImplementor getSession() { */ void forceFlush(EntityKey e) throws HibernateException; - /** - * Cascade the lock operation to the given child entity. - */ - void lock(String entityName, Object child, LockOptions lockOptions); - @Override default SessionImplementor asSessionImplementor() { return this; 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 6ae7a2a9fd23..b58a56c7842a 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 @@ -12,7 +12,10 @@ import org.hibernate.FlushMode; import org.hibernate.HibernateException; +import org.hibernate.Incubating; import org.hibernate.Interceptor; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; import org.hibernate.StatelessSession; import org.hibernate.boot.spi.SessionFactoryOptions; import org.hibernate.dialect.Dialect; @@ -584,4 +587,23 @@ default boolean isStatelessSession() { return false; } + /** + * Cascade the lock operation to the given child entity. + */ + void lock(String entityName, Object child, LockOptions lockOptions); + + /** + * Attempts to load the entity from the second-level cache. + * + * @param persister The persister for the entity being requested for load + * @param entityKey The entity key + * @param instanceToLoad The instance that is being initialized, or null + * @param lockMode The lock mode + * + * @return The entity from the second-level cache, or null. + * + * @since 7.0 + */ + @Incubating + Object loadFromSecondLevelCache(EntityPersister persister, EntityKey entityKey, Object instanceToLoad, LockMode lockMode); } 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 b42cf98efd11..920ff90b6a77 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 @@ -17,6 +17,8 @@ import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.Interceptor; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; import org.hibernate.SharedSessionContract; import org.hibernate.Transaction; import org.hibernate.cache.spi.CacheTransactionSynchronization; @@ -691,4 +693,14 @@ public FormatMapper getJsonFormatMapper() { public FormatMapper getXmlFormatMapper() { return delegate.getXmlFormatMapper(); } + + @Override + public void lock(String entityName, Object child, LockOptions lockOptions) { + delegate.lock( entityName, child, lockOptions ); + } + + @Override + public Object loadFromSecondLevelCache(EntityPersister persister, EntityKey entityKey, Object instanceToLoad, LockMode lockMode) { + return delegate.loadFromSecondLevelCache( persister, entityKey, instanceToLoad, lockMode ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/event/spi/EventSource.java b/hibernate-core/src/main/java/org/hibernate/event/spi/EventSource.java index 4794b8ba2c42..5aba62a74464 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/spi/EventSource.java +++ b/hibernate-core/src/main/java/org/hibernate/event/spi/EventSource.java @@ -5,8 +5,6 @@ package org.hibernate.event.spi; import org.hibernate.HibernateException; -import org.hibernate.Incubating; -import org.hibernate.LockMode; import org.hibernate.engine.spi.ActionQueue; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityKey; @@ -70,18 +68,4 @@ public interface EventSource extends SessionImplementor { // This should be removed once action/task ordering is improved. void removeOrphanBeforeUpdates(String entityName, Object child); - /** - * Attempts to load the entity from the second-level cache. - * - * @param persister The persister for the entity being requested for load - * @param entityKey The entity key - * @param instanceToLoad The instance that is being initialized, or null - * @param lockMode The lock mode - * - * @return The entity from the second-level cache, or null. - * - * @since 7.0 - */ - @Incubating - Object loadFromSecondLevelCache(EntityPersister persister, EntityKey entityKey, Object instanceToLoad, LockMode lockMode); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/MultiIdentifierLoadAccessImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/MultiIdentifierLoadAccessImpl.java index 54a89973573e..ffab763a7de9 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/MultiIdentifierLoadAccessImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/MultiIdentifierLoadAccessImpl.java @@ -16,6 +16,7 @@ import org.hibernate.engine.spi.EffectiveEntityGraph; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; @@ -28,7 +29,7 @@ * @author Steve Ebersole */ class MultiIdentifierLoadAccessImpl implements MultiIdentifierLoadAccess, MultiIdLoadOptions { - private final SessionImpl session; + private final SharedSessionContractImplementor session; private final EntityPersister entityPersister; private LockOptions lockOptions; @@ -46,7 +47,7 @@ class MultiIdentifierLoadAccessImpl implements MultiIdentifierLoadAccess, private Set enabledFetchProfiles; private Set disabledFetchProfiles; - public MultiIdentifierLoadAccessImpl(SessionImpl session, EntityPersister entityPersister) { + public MultiIdentifierLoadAccessImpl(SharedSessionContractImplementor session, EntityPersister entityPersister) { this.session = session; this.entityPersister = entityPersister; } @@ -88,12 +89,7 @@ public Integer getBatchSize() { @Override public MultiIdentifierLoadAccess withBatchSize(int batchSize) { - if ( batchSize < 1 ) { - this.batchSize = null; - } - else { - this.batchSize = batchSize; - } + this.batchSize = batchSize < 1 ? null : batchSize; return this; } @@ -185,16 +181,9 @@ public List perform(Supplier> executor) { @Override @SuppressWarnings( "unchecked" ) public List multiLoad(List ids) { - if ( ids.isEmpty() ) { - return emptyList(); - } - else { - return perform( () -> (List) entityPersister.multiLoad( - ids.toArray( new Object[0] ), - session, - this - ) ); - } + return ids.isEmpty() + ? emptyList() + : perform( () -> (List) entityPersister.multiLoad( ids.toArray(), session, this ) ); } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java index 2147fe0610df..645879e57ffa 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java @@ -12,6 +12,7 @@ import org.hibernate.NaturalIdMultiLoadAccess; import org.hibernate.engine.spi.EffectiveEntityGraph; import org.hibernate.engine.spi.LoadQueryInfluencers; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.RootGraph; import org.hibernate.graph.spi.RootGraphImplementor; @@ -24,7 +25,7 @@ */ public class NaturalIdMultiLoadAccessStandard implements NaturalIdMultiLoadAccess, MultiNaturalIdLoadOptions { private final EntityPersister entityDescriptor; - private final SessionImpl session; + private final SharedSessionContractImplementor session; private LockOptions lockOptions; private CacheMode cacheMode; @@ -36,7 +37,7 @@ public class NaturalIdMultiLoadAccessStandard implements NaturalIdMultiLoadAc private boolean returnOfDeletedEntitiesEnabled; private boolean orderedReturnEnabled = true; - public NaturalIdMultiLoadAccessStandard(EntityPersister entityDescriptor, SessionImpl session) { + public NaturalIdMultiLoadAccessStandard(EntityPersister entityDescriptor, SharedSessionContractImplementor session) { this.entityDescriptor = entityDescriptor; this.session = session; } 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 5b5d4a56ceab..d1ddf0045344 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java @@ -9,9 +9,11 @@ import java.util.Set; import java.util.function.BiConsumer; +import org.hibernate.AssertionFailure; import org.hibernate.FlushMode; import org.hibernate.HibernateException; import org.hibernate.LockMode; +import org.hibernate.LockOptions; import org.hibernate.SessionException; import org.hibernate.StatelessSession; import org.hibernate.TransientObjectException; @@ -26,10 +28,12 @@ import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.engine.spi.CollectionEntry; import org.hibernate.engine.spi.EffectiveEntityGraph; +import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.transaction.internal.jta.JtaStatusHelper; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; @@ -70,19 +74,19 @@ import org.hibernate.graph.GraphSemantic; import org.hibernate.graph.spi.RootGraphImplementor; import org.hibernate.id.IdentifierGenerationException; +import org.hibernate.loader.ast.internal.LoaderHelper; import org.hibernate.loader.ast.spi.CascadingFetchProfile; +import org.hibernate.loader.ast.spi.MultiIdLoadOptions; +import org.hibernate.loader.internal.CacheLoadHelper; import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.proxy.LazyInitializer; -import org.hibernate.query.criteria.JpaCriteriaQuery; -import org.hibernate.query.criteria.JpaRoot; import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.tuple.entity.EntityMetamodel; import jakarta.persistence.EntityGraph; import jakarta.transaction.SystemException; -import static java.util.Collections.unmodifiableList; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; import static org.hibernate.engine.internal.PersistenceContexts.createPersistenceContext; @@ -93,7 +97,6 @@ import static org.hibernate.generator.EventType.INSERT; import static org.hibernate.internal.util.NullnessUtil.castNonNull; import static org.hibernate.loader.internal.CacheLoadHelper.initializeCollectionFromCache; -import static org.hibernate.loader.internal.CacheLoadHelper.loadFromSecondLevelCache; import static org.hibernate.pretty.MessageHelper.collectionInfoString; import static org.hibernate.pretty.MessageHelper.infoString; import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer; @@ -125,6 +128,8 @@ public class StatelessSessionImpl extends AbstractSharedSessionContract implements StatelessSession { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( StatelessSessionImpl.class ); + public static final MultiIdLoadOptions MULTI_ID_LOAD_OPTIONS = new MultiLoadOptions(); + private final LoadQueryInfluencers influencers; private final PersistenceContext temporaryPersistenceContext; private final boolean connectionProvided; @@ -715,8 +720,7 @@ public Object get(String entityName, Object id, LockMode lockMode) { final EntityPersister persister = requireEntityPersister( entityName ); if ( persister.canReadFromCache() ) { final Object cachedEntity = - loadFromSecondLevelCache( this, null, lockMode, persister, - generateEntityKey( id, persister ) ); + loadFromSecondLevelCache( persister, generateEntityKey( id, persister ), null, lockMode ); if ( cachedEntity != null ) { temporaryPersistenceContext.clear(); return cachedEntity; @@ -764,53 +768,71 @@ public T get( } @Override - public List getMultiple(Class entityClass, List ids) { - for (Object id : ids) { + public List getMultiple(Class entityClass, List ids, LockMode lockMode) { + for ( Object id : ids ) { if ( id == null ) { - throw new IllegalArgumentException("Null id"); + throw new IllegalArgumentException( "Null id" ); } } final EntityPersister persister = requireEntityPersister( entityClass.getName() ); - final List uncachedIds; - final List list = new ArrayList<>( ids.size() ); - if ( persister.canReadFromCache() ) { - uncachedIds = new ArrayList<>( ids.size() ); - for (Object id : ids) { - final Object cachedEntity = - loadFromSecondLevelCache( this, null, LockMode.NONE, persister, - generateEntityKey( id, persister ) ); - if ( cachedEntity == null ) { - uncachedIds.add( id ); - list.add( null ); - } - else { - //noinspection unchecked - list.add( (T) cachedEntity ); - } - } - } - else { - uncachedIds = unmodifiableList(ids); - for (int i = 0; i < ids.size(); i++) { - list.add( null ); - } - } + final List results = persister.multiLoad( ids.toArray(), this, new MultiLoadOptions(lockMode) ); + //noinspection unchecked + return (List) results; + } - final JpaCriteriaQuery query = getCriteriaBuilder().createQuery(entityClass); - final JpaRoot from = query.from(entityClass); - query.where( from.get( persister.getIdentifierPropertyName() ).in(uncachedIds) ); - final List resultList = createSelectionQuery(query).getResultList(); - for (int i = 0; i < ids.size(); i++) { - if ( list.get(i) == null ) { - final Object id = ids.get(i); - list.set( i, resultList.stream() - .filter( entity -> entity != null && persister.getIdentifier( entity, this ).equals(id) ) - .findFirst().orElse( null ) ); + @Override + public List getMultiple(Class entityClass, List ids) { + for ( Object id : ids ) { + if ( id == null ) { + throw new IllegalArgumentException("Null id"); } } - return list; + + final EntityPersister persister = requireEntityPersister( entityClass.getName() ); + + final List results = persister.multiLoad( ids.toArray(), this, MULTI_ID_LOAD_OPTIONS ); + //noinspection unchecked + return (List) results; + +// final List uncachedIds; +// final List list = new ArrayList<>( ids.size() ); +// if ( persister.canReadFromCache() ) { +// uncachedIds = new ArrayList<>( ids.size() ); +// for (Object id : ids) { +// final Object cachedEntity = +// loadFromSecondLevelCache( persister, generateEntityKey( id, persister ), null, LockMode.NONE ); +// if ( cachedEntity == null ) { +// uncachedIds.add( id ); +// list.add( null ); +// } +// else { +// //noinspection unchecked +// list.add( (T) cachedEntity ); +// } +// } +// } +// else { +// uncachedIds = unmodifiableList(ids); +// for (int i = 0; i < ids.size(); i++) { +// list.add( null ); +// } +// } +// +// final JpaCriteriaQuery query = getCriteriaBuilder().createQuery(entityClass); +// final JpaRoot from = query.from(entityClass); +// query.where( from.get( persister.getIdentifierPropertyName() ).in(uncachedIds) ); +// final List resultList = createSelectionQuery(query).getResultList(); +// for (int i = 0; i < ids.size(); i++) { +// if ( list.get(i) == null ) { +// final Object id = ids.get(i); +// list.set( i, resultList.stream() +// .filter( entity -> entity != null && persister.getIdentifier( entity, this ).equals(id) ) +// .findFirst().orElse( null ) ); +// } +// } +// return list; } @Override @@ -1348,4 +1370,67 @@ protected void removeCacheItem(Object ck, CollectionPersister persister) { persister.getCacheAccessStrategy().remove( this, ck ); } } + + @Override + public void lock(String entityName, Object child, LockOptions lockOptions) { + final EntityPersister persister = getEntityPersister( entityName, child ); + persister.lock( persister.getIdentifier( child ), persister.getVersion( child ), child, lockOptions, this ); + final EntityEntry entry = getPersistenceContextInternal().getEntry( child ); + if ( entry == null ) { + throw new AssertionFailure( "no entry in temporary persistence context" ); + } + LoaderHelper.upgradeLock( child, entry, lockOptions, this ); + } + + @Override + public Object loadFromSecondLevelCache(EntityPersister persister, EntityKey entityKey, Object instanceToLoad, LockMode lockMode) { + return CacheLoadHelper.loadFromSecondLevelCache( this, instanceToLoad, lockMode, persister, entityKey ); + } + + private static final class MultiLoadOptions implements MultiIdLoadOptions { + private final LockOptions lockOptions; + + private MultiLoadOptions() { + this.lockOptions = null; + } + + private MultiLoadOptions(LockMode lockOptions) { + this.lockOptions = new LockOptions( lockOptions ); + } + + @Override + public boolean isSessionCheckingEnabled() { + return false; + } + + @Override + public boolean isSecondLevelCacheCheckingEnabled() { + return true; + } + + @Override + public Boolean getReadOnly(SessionImplementor session) { + return null; + } + + @Override + public boolean isReturnOfDeletedEntitiesEnabled() { + return false; + } + + @Override + public boolean isOrderReturnEnabled() { + return true; + } + + @Override + public LockOptions getLockOptions() { + return lockOptions; + } + + @Override + public Integer getBatchSize() { + return null; + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java index 5b55f444ea07..fe8ccb561bcd 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/AbstractMultiIdEntityLoader.java @@ -12,7 +12,7 @@ import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.loader.ast.spi.MultiIdEntityLoader; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.loader.internal.CacheLoadHelper.PersistenceContextEntry; @@ -78,7 +78,7 @@ public EntityMappingType getLoadable() { } @Override - public final List load(K[] ids, MultiIdLoadOptions loadOptions, EventSource session) { + public final List load(K[] ids, MultiIdLoadOptions loadOptions, SharedSessionContractImplementor session) { assert ids != null; return loadOptions.isOrderReturnEnabled() ? performOrderedMultiLoad( ids, loadOptions, session ) @@ -88,7 +88,7 @@ public final List load(K[] ids, MultiIdLoadOptions loadOptions, EventSour private List performUnorderedMultiLoad( Object[] ids, MultiIdLoadOptions loadOptions, - EventSource session) { + SharedSessionContractImplementor session) { assert !loadOptions.isOrderReturnEnabled(); assert ids != null; if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { @@ -100,7 +100,7 @@ private List performUnorderedMultiLoad( protected List performOrderedMultiLoad( Object[] ids, MultiIdLoadOptions loadOptions, - EventSource session) { + SharedSessionContractImplementor session) { assert loadOptions.isOrderReturnEnabled(); assert ids != null; if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { @@ -113,7 +113,7 @@ private List orderedMultiLoad( Object[] ids, MultiIdLoadOptions loadOptions, LockOptions lockOptions, - EventSource session) { + SharedSessionContractImplementor session) { final boolean idCoercionEnabled = isIdCoercionEnabled(); final JavaType idType = getLoadable().getIdentifierMapping().getJavaType(); @@ -168,7 +168,7 @@ protected static LockOptions lockOptions(MultiIdLoadOptions loadOptions) { protected void handleResults( MultiIdLoadOptions loadOptions, - EventSource session, + SharedSessionContractImplementor session, List elementPositionsLoadedByBatch, List results) { final PersistenceContext persistenceContext = session.getPersistenceContext(); @@ -197,11 +197,11 @@ protected abstract void loadEntitiesById( List idsInBatch, LockOptions lockOptions, MultiIdLoadOptions loadOptions, - EventSource session); + SharedSessionContractImplementor session); protected boolean loadFromEnabledCaches( MultiIdLoadOptions loadOptions, - EventSource session, + SharedSessionContractImplementor session, Object id, LockOptions lockOptions, EntityKey entityKey, @@ -216,7 +216,7 @@ private boolean isLoadFromCaches( EntityKey entityKey, LockOptions lockOptions, List results, int i, - EventSource session) { + SharedSessionContractImplementor session) { if ( loadOptions.isSessionCheckingEnabled() ) { // look for it in the Session first @@ -251,7 +251,7 @@ protected List unorderedMultiLoad( Object[] ids, MultiIdLoadOptions loadOptions, LockOptions lockOptions, - EventSource session) { + SharedSessionContractImplementor session) { final List result = arrayList( ids.length ); final Object[] unresolvableIds = resolveInCachesIfEnabled( ids, loadOptions, lockOptions, session, @@ -275,7 +275,7 @@ protected List unorderedMultiLoad( protected abstract void loadEntitiesWithUnresolvedIds( MultiIdLoadOptions loadOptions, LockOptions lockOptions, - EventSource session, + SharedSessionContractImplementor session, Object[] unresolvableIds, List results); @@ -283,7 +283,7 @@ protected final Object[] resolveInCachesIfEnabled( Object[] ids, @NonNull MultiIdLoadOptions loadOptions, @NonNull LockOptions lockOptions, - EventSource session, + SharedSessionContractImplementor session, ResolutionConsumer resolutionConsumer) { return loadOptions.isSessionCheckingEnabled() || loadOptions.isSecondLevelCacheCheckingEnabled() // the user requested that we exclude ids corresponding to already managed @@ -300,7 +300,7 @@ protected final Object[] resolveInCaches( Object[] ids, MultiIdLoadOptions loadOptions, LockOptions lockOptions, - EventSource session, + SharedSessionContractImplementor session, ResolutionConsumer resolutionConsumer) { final boolean idCoercionEnabled = isIdCoercionEnabled(); @@ -346,7 +346,7 @@ private List loadFromCaches( Object id, EntityKey entityKey, List unresolvedIds, int i, - EventSource session) { + SharedSessionContractImplementor session) { // look for it in the Session first final PersistenceContextEntry entry = @@ -383,7 +383,7 @@ private List loadFromCaches( return unresolvedIds; } - private Object loadFromSecondLevelCache(EntityKey entityKey, LockOptions lockOptions, EventSource session) { + private Object loadFromSecondLevelCache(EntityKey entityKey, LockOptions lockOptions, SharedSessionContractImplementor session) { final EntityPersister persister = getLoadable().getEntityPersister(); return session.loadFromSecondLevelCache( persister, entityKey, null, lockOptions.getLockMode() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java index f4d7648ce4a0..c6abd8730dd6 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/LoaderHelper.java @@ -54,7 +54,8 @@ public class LoaderHelper { * @param lockOptions Contains the requested lock mode. * @param session The session which is the source of the event being processed. */ - public static void upgradeLock(Object object, EntityEntry entry, LockOptions lockOptions, EventSource session) { + public static void upgradeLock( + Object object, EntityEntry entry, LockOptions lockOptions, SharedSessionContractImplementor session) { final LockMode requestedLockMode = lockOptions.getLockMode(); if ( requestedLockMode.greaterThan( entry.getLockMode() ) ) { // Request is for a more restrictive lock than the lock already held @@ -108,22 +109,23 @@ public static void upgradeLock(Object object, EntityEntry entry, LockOptions loc persister.forceVersionIncrement( entry.getId(), entry.getVersion(), false, session ); entry.forceLocked( object, nextVersion ); } - else { - if ( entry.isExistsInDatabase() ) { - final EventMonitor eventMonitor = session.getEventMonitor(); - final DiagnosticEvent entityLockEvent = eventMonitor.beginEntityLockEvent(); - boolean success = false; - try { - persister.lock( entry.getId(), entry.getVersion(), object, lockOptions, session ); - success = true; - } - finally { - eventMonitor.completeEntityLockEvent( entityLockEvent, entry.getId(), - persister.getEntityName(), lockOptions.getLockMode(), success, session ); - } + else if ( entry.isExistsInDatabase() ) { + final EventMonitor eventMonitor = session.getEventMonitor(); + final DiagnosticEvent entityLockEvent = eventMonitor.beginEntityLockEvent(); + boolean success = false; + try { + persister.lock( entry.getId(), entry.getVersion(), object, lockOptions, session ); + success = true; } - else { - session.forceFlush( entry ); + finally { + eventMonitor.completeEntityLockEvent( entityLockEvent, entry.getId(), + persister.getEntityName(), lockOptions.getLockMode(), success, session ); + } + } + else { + // should only be possible for a stateful session + if ( session instanceof EventSource eventSource ) { + eventSource.forceFlush( entry ); } } entry.setLockMode(requestedLockMode); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java index 5a6d67fd6487..04d1c2952a78 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java @@ -10,7 +10,8 @@ import org.hibernate.LockOptions; import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.internal.build.AllowReflection; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.loader.ast.spi.SqlArrayMultiKeyLoader; @@ -80,7 +81,7 @@ protected void loadEntitiesById( List idsInBatch, LockOptions lockOptions, MultiIdLoadOptions loadOptions, - EventSource session) { + SharedSessionContractImplementor session) { final SelectStatement sqlAst = createSelectBySingleArrayParameter( getLoadable(), getIdentifierMapping(), @@ -111,7 +112,9 @@ public LockOptions getLockOptions() { JdbcParametersList.singleton( jdbcParameter ), jdbcParameterBindings ), - TRUE.equals( loadOptions.getReadOnly( session ) ), + // stateless sessions don't have a read-only mode + session instanceof SessionImplementor statefulSession + && TRUE.equals( loadOptions.getReadOnly( statefulSession ) ), lockOptions ), RowTransformerStandardImpl.instance(), @@ -125,7 +128,7 @@ public LockOptions getLockOptions() { protected void loadEntitiesWithUnresolvedIds( MultiIdLoadOptions loadOptions, LockOptions lockOptions, - EventSource session, + SharedSessionContractImplementor session, Object[] unresolvableIds, List result) { final SelectStatement sqlAst = createSelectBySingleArrayParameter( diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderInPredicate.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderInPredicate.java index ec299cec2fe7..0a01d0b25596 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderInPredicate.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderInPredicate.java @@ -9,9 +9,9 @@ import org.hibernate.LockOptions; import org.hibernate.engine.spi.BatchFetchQueue; import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; -import org.hibernate.event.spi.EventSource; import org.hibernate.loader.ast.spi.MultiIdLoadOptions; import org.hibernate.loader.ast.spi.MultiKeyLoadSizingStrategy; import org.hibernate.persister.entity.EntityPersister; @@ -73,7 +73,7 @@ protected void loadEntitiesById( List idsInBatch, LockOptions lockOptions, MultiIdLoadOptions loadOptions, - EventSource session) { + SharedSessionContractImplementor session) { assert idsInBatch != null; assert !idsInBatch.isEmpty(); listEntitiesById( idsInBatch, lockOptions, loadOptions, session ); @@ -83,7 +83,7 @@ private List listEntitiesById( List idsInBatch, LockOptions lockOptions, MultiIdLoadOptions loadOptions, - EventSource session) { + SharedSessionContractImplementor session) { final int numberOfIdsInBatch = idsInBatch.size(); return numberOfIdsInBatch == 1 ? performSingleMultiLoad( idsInBatch.get( 0 ), lockOptions, session ) @@ -94,7 +94,7 @@ private List performRegularMultiLoad( List idsInBatch, LockOptions lockOptions, MultiIdLoadOptions loadOptions, - EventSource session, + SharedSessionContractImplementor session, int numberOfIdsInBatch) { if ( MULTI_KEY_LOAD_LOGGER.isTraceEnabled() ) { MULTI_KEY_LOAD_LOGGER.tracef( "#loadEntitiesById(`%s`, `%s`, ..)", @@ -144,7 +144,8 @@ public LockOptions getLockOptions() { new ExecutionContextWithSubselectFetchHandler( session, fetchableKeysHandler( session, sqlAst, jdbcParameters, jdbcParameterBindings ), - TRUE.equals( loadOptions.getReadOnly( session ) ), + session instanceof SessionImplementor statefulSession + && TRUE.equals( loadOptions.getReadOnly( statefulSession ) ), lockOptions ), RowTransformerStandardImpl.instance(), @@ -155,7 +156,7 @@ public LockOptions getLockOptions() { } private SubselectFetch.RegistrationHandler fetchableKeysHandler( - EventSource session, + SharedSessionContractImplementor session, SelectStatement sqlAst, JdbcParametersList jdbcParameters, JdbcParameterBindings jdbcParameterBindings) { @@ -175,7 +176,7 @@ private List performSingleMultiLoad(Object id, LockOptions lockOptions, Share protected void loadEntitiesWithUnresolvedIds( MultiIdLoadOptions loadOptions, LockOptions lockOptions, - EventSource session, + SharedSessionContractImplementor session, Object[] unresolvableIds, List result) { final int maxBatchSize = maxBatchSize( unresolvableIds, loadOptions ); diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdEntityLoader.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdEntityLoader.java index f96b22e5f077..0063236946a9 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdEntityLoader.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/spi/MultiIdEntityLoader.java @@ -6,7 +6,7 @@ import java.util.List; -import org.hibernate.event.spi.EventSource; +import org.hibernate.engine.spi.SharedSessionContractImplementor; /** * Loader subtype for loading multiple entities by multiple identifier values. @@ -15,5 +15,5 @@ public interface MultiIdEntityLoader extends EntityMultiLoader { /** * Load multiple entities by id. The exact result depends on the passed options. */ - List load(K[] ids, MultiIdLoadOptions options, EventSource session); + List load(K[] ids, MultiIdLoadOptions options, SharedSessionContractImplementor session); } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/internal/CacheLoadHelper.java b/hibernate-core/src/main/java/org/hibernate/loader/internal/CacheLoadHelper.java index 026e234997df..d7530ebe7fa6 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/internal/CacheLoadHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/internal/CacheLoadHelper.java @@ -24,7 +24,6 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.Status; -import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.LoadEventListener; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.collection.CollectionPersister; @@ -81,7 +80,7 @@ private CacheLoadHelper() { public static PersistenceContextEntry loadFromSessionCache( EntityKey keyToLoad, LockOptions lockOptions, LoadEventListener.LoadType options, - EventSource session) { + SharedSessionContractImplementor session) { final Object old = session.getEntityUsingInterceptor( keyToLoad ); if ( old != null ) { // this object was already loaded diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index 9bcca0790ba0..af0679fb7e9d 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -263,7 +263,6 @@ import java.util.Arrays; import java.util.BitSet; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -282,6 +281,7 @@ import static java.util.Collections.emptyList; import static java.util.Collections.emptyMap; import static java.util.Collections.emptySet; +import static java.util.Collections.unmodifiableList; import static org.hibernate.boot.model.internal.SoftDeleteHelper.resolveSoftDeleteMapping; import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable; import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable; @@ -292,7 +292,9 @@ import static org.hibernate.generator.EventType.UPDATE; import static org.hibernate.internal.util.ReflectHelper.isAbstractClass; import static org.hibernate.internal.util.StringHelper.isEmpty; +import static org.hibernate.internal.util.StringHelper.qualify; import static org.hibernate.internal.util.StringHelper.qualifyConditionally; +import static org.hibernate.internal.util.StringHelper.unqualify; import static org.hibernate.internal.util.collections.ArrayHelper.contains; import static org.hibernate.internal.util.collections.ArrayHelper.isAllTrue; import static org.hibernate.internal.util.collections.ArrayHelper.to2DStringArray; @@ -845,21 +847,21 @@ public static Map getEntityNameByTableNameMap( return entityNameByTableNameMap; } - protected MultiIdEntityLoader buildMultiIdLoader() { - final Dialect dialect = factory.getJdbcServices().getDialect(); - return getIdentifierType() instanceof BasicType && supportsSqlArrayType( dialect ) + /** + * Used by Hibernate Reactive + */ + protected MultiIdEntityLoader buildMultiIdLoader() { + return getIdentifierType() instanceof BasicType + && supportsSqlArrayType( getDialect() ) ? new MultiIdEntityLoaderArrayParam<>( this, factory ) : new MultiIdEntityLoaderInPredicate<>( this, identifierColumnSpan, factory ); } private String getIdentitySelectString(Dialect dialect) { try { + final int idTypeCode = ((BasicType) getIdentifierType()).getJdbcType().getDdlTypeCode(); return dialect.getIdentityColumnSupport() - .getIdentitySelectString( - getTableName(0), - getKeyColumns(0)[0], - ( (BasicType) getIdentifierType() ).getJdbcType().getDdlTypeCode() - ); + .getIdentitySelectString( getTableName(0), getKeyColumns(0)[0], idTypeCode ); } catch (MappingException ex) { return null; @@ -1710,8 +1712,6 @@ protected boolean initializeLazyProperty( return fieldName.equals( lazyPropertyNames[index] ); } - - protected boolean initializeLazyProperty( final String fieldName, final Object entity, @@ -1905,21 +1905,15 @@ public String selectFragment(String alias, String suffix) { ); } - final String sql = getFactory().getJdbcServices() - .getDialect() - .getSqlAstTranslatorFactory() - .buildSelectTranslator( getFactory(), new SelectStatement( rootQuerySpec ) ) - .translate( null, QueryOptions.NONE ) - .getSqlString(); + final String sql = + getDialect().getSqlAstTranslatorFactory() + .buildSelectTranslator( getFactory(), new SelectStatement( rootQuerySpec ) ) + .translate( null, QueryOptions.NONE ) + .getSqlString(); final int fromIndex = sql.lastIndexOf( " from" ); - final String expression; - if ( fromIndex != -1 ) { - expression = sql.substring( "select ".length(), fromIndex ); - } - else { - expression = sql.substring( "select ".length() ); - } - return expression; + return fromIndex != -1 + ? sql.substring( "select ".length(), fromIndex ) + : sql.substring( "select ".length() ); } private ImmutableFetchList fetchProcessor(FetchParent fetchParent, LoaderSqlAstCreationState creationState) { @@ -2137,7 +2131,7 @@ public JdbcMapping getJdbcMapping(int index) { } protected LockingStrategy generateLocker(LockMode lockMode) { - return factory.getJdbcServices().getDialect().getLockingStrategy( this, lockMode ); + return getDialect().getLockingStrategy( this, lockMode ); } private LockingStrategy getLocker(LockMode lockMode) { @@ -2150,20 +2144,30 @@ public void lock( Object version, Object object, LockMode lockMode, - EventSource session) throws HibernateException { + SharedSessionContractImplementor session) throws HibernateException { getLocker( lockMode ).lock( id, version, object, LockOptions.WAIT_FOREVER, session ); } + @Override + public void lock(Object id, Object version, Object object, LockMode lockMode, EventSource session) { + lock( id, version, object, lockMode, (SharedSessionContractImplementor) session ); + } + @Override public void lock( Object id, Object version, Object object, LockOptions lockOptions, - EventSource session) throws HibernateException { + SharedSessionContractImplementor session) throws HibernateException { getLocker( lockOptions.getLockMode() ).lock( id, version, object, lockOptions.getTimeOut(), session ); } + @Override + public void lock(Object id, Object version, Object object, LockOptions lockOptions, EventSource session) { + lock( id, version, object, lockOptions, (SharedSessionContractImplementor) session ); + } + @Override public String getRootTableName() { return getSubclassTableName( 0 ); @@ -2204,41 +2208,6 @@ public String[] getPropertyColumnNames(String propertyName) { return propertyMapping.getColumnNames( propertyName ); } - /** - * Warning: - * When there are duplicated property names in the subclasses - * of the class, this method may return the wrong table - * number for the duplicated subclass property. Note that - * SingleTableEntityPersister defines an overloaded form - * which takes the entity name. - */ - public int getSubclassPropertyTableNumber(String propertyPath) { - throw new UnsupportedOperationException(); -// String rootPropertyName = StringHelper.root( propertyPath ); -// Type type = propertyMapping.toType( rootPropertyName ); -// if ( type.isAssociationType() ) { -// AssociationType assocType = (AssociationType) type; -// if ( assocType.useLHSPrimaryKey() ) { -// // performance op to avoid the array search -// return 0; -// } -// else if ( type instanceof CollectionType ) { -// // properly handle property-ref-based associations -// rootPropertyName = assocType.getLHSPropertyName(); -// } -// } -// //Enable for HHH-440, which we don't like: -// /*if ( type.isComponentType() && !propertyName.equals(rootPropertyName) ) { -// String unrooted = StringHelper.unroot(propertyName); -// int idx = ArrayHelper.indexOf( getSubclassColumnClosure(), unrooted ); -// if ( idx != -1 ) { -// return getSubclassColumnTableNumberClosure()[idx]; -// } -// }*/ -// int index = ArrayHelper.indexOf( getSubclassPropertyNameClosure(), rootPropertyName ); //TODO: optimize this better! -// return index == -1 ? 0 : getSubclassPropertyTableNumber( index ); - } - private DiscriminatorType discriminatorType; protected DiscriminatorType resolveDiscriminatorType() { @@ -2626,8 +2595,8 @@ public String getSelectByUniqueKeyString(String[] propertyNames) { final SimpleSelect select = new SimpleSelect( getFactory() ) .setTableName( getTableName(0) ) .addColumns( getKeyColumns(0) ); - for ( int i = 0; i < propertyNames.length; i++ ) { - select.addRestriction( getPropertyColumnNames( propertyNames[i] ) ); + for ( String propertyName : propertyNames ) { + select.addRestriction( getPropertyColumnNames( propertyName ) ); } return select.toStatementString(); } @@ -3171,10 +3140,10 @@ private void doLateInit() { final List insertGeneratedAttributes = hasInsertGeneratedProperties() ? GeneratedValuesProcessor.getGeneratedAttributes( this, INSERT ) - : Collections.emptyList(); + : emptyList(); final List updateGeneratedAttributes = hasUpdateGeneratedProperties() ? GeneratedValuesProcessor.getGeneratedAttributes( this, UPDATE ) - : Collections.emptyList(); + : emptyList(); insertGeneratedProperties = initInsertGeneratedProperties( insertGeneratedAttributes ); updateGeneratedProperties = initUpdateGeneratedProperties( updateGeneratedAttributes ); @@ -3183,7 +3152,7 @@ private void doLateInit() { updateDelegate = createUpdateDelegate(); if ( isIdentifierAssignedByInsert() ) { - identitySelectString = getIdentitySelectString( factory.getJdbcServices().getDialect() ); + identitySelectString = getIdentitySelectString( getDialect() ); } if ( hasInsertGeneratedProperties() ) { @@ -3398,7 +3367,7 @@ protected EntityTableMapping[] buildTableMappings() { * Consumer for processing table details. Used while {@linkplain #buildTableMappings() building} * the {@link EntityTableMapping} descriptors. */ - interface MutabilityOrderedTableConsumer { + protected interface MutabilityOrderedTableConsumer { void consume( String tableExpression, int relativePosition, @@ -3584,6 +3553,11 @@ else if ( interceptor.isAttributeLoaded( nameOfAttributeBeingAccessed ) ) { @Override public List multiLoad(Object[] ids, EventSource session, MultiIdLoadOptions loadOptions) { + return multiLoad( ids, (SharedSessionContractImplementor) session, loadOptions ); + } + + @Override + public List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) { return multiIdLoader.load( ids, loadOptions, session ); } @@ -3619,13 +3593,13 @@ public boolean isAffectedByEnabledFilters( LoadQueryInfluencers loadQueryInfluencers, boolean onlyApplyForLoadByKeyFilters) { if ( filterHelper != null && loadQueryInfluencers.hasEnabledFilters() ) { - if ( filterHelper.isAffectedBy( loadQueryInfluencers.getEnabledFilters(), onlyApplyForLoadByKeyFilters ) ) { - return true; - } + return filterHelper.isAffectedBy( loadQueryInfluencers.getEnabledFilters(), onlyApplyForLoadByKeyFilters ) + || isAffectedByEnabledFilters( new HashSet<>(), loadQueryInfluencers, onlyApplyForLoadByKeyFilters ); - return isAffectedByEnabledFilters( new HashSet<>(), loadQueryInfluencers, onlyApplyForLoadByKeyFilters ); } - return false; + else { + return false; + } } /** @@ -3672,7 +3646,7 @@ public int[] findDirty(Object[] currentState, Object[] previousState, Object ent @Override public int[] findModified(Object[] old, Object[] current, Object entity, SharedSessionContractImplementor session) throws HibernateException { - int[] props = DirtyHelper.findModified( + final int[] modified = DirtyHelper.findModified( entityMetamodel.getProperties(), current, old, @@ -3680,12 +3654,12 @@ public int[] findModified(Object[] old, Object[] current, Object entity, SharedS getPropertyUpdateability(), session ); - if ( props == null ) { + if ( modified == null ) { return null; } else { - logDirtyProperties( props ); - return props; + logDirtyProperties( modified ); + return modified; } } @@ -3703,7 +3677,7 @@ private void logDirtyProperties(int[] props) { if ( LOG.isTraceEnabled() ) { for ( int prop : props ) { final String propertyName = getAttributeMapping( prop ).getAttributeName(); - LOG.trace( StringHelper.qualify( getEntityName(), propertyName ) + " is dirty" ); + LOG.trace( qualify( getEntityName(), propertyName ) + " is dirty" ); } } } @@ -3713,6 +3687,10 @@ public SessionFactoryImplementor getFactory() { return factory; } + private Dialect getDialect() { + return factory.getJdbcServices().getDialect(); + } + @Override public EntityMetamodel getEntityMetamodel() { return entityMetamodel; @@ -3838,28 +3816,24 @@ public void afterReassociate(Object entity, SharedSessionContractImplementor ses } private void handleNaturalIdReattachment(Object entity, SharedSessionContractImplementor session) { - if ( naturalIdMapping == null ) { - return; - } - - if ( ! naturalIdMapping.isMutable() ) { - // we assume there were no changes to natural id during detachment for now, that is validated later - // during flush. - return; + if ( naturalIdMapping != null ) { + if ( naturalIdMapping.isMutable() ) { + final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); + final NaturalIdResolutions naturalIdResolutions = persistenceContext.getNaturalIdResolutions(); + final Object id = getIdentifier( entity, session ); + + // for reattachment of mutable natural-ids, we absolutely positively have to grab the snapshot from the + // database, because we have no other way to know if the state changed while detached. + final Object[] entitySnapshot = persistenceContext.getDatabaseSnapshot( id, this ); + final Object naturalIdSnapshot = naturalIdFromSnapshot( entitySnapshot ); + + naturalIdResolutions.removeSharedResolution( id, naturalIdSnapshot, this, false ); + final Object naturalId = naturalIdMapping.extractNaturalIdFromEntity( entity ); + naturalIdResolutions.manageLocalResolution( id, naturalId, this, CachedNaturalIdValueSource.UPDATE ); + } + // otherwise we assume there were no changes to natural id during detachment for now, + // that is validated later during flush. } - - final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); - final NaturalIdResolutions naturalIdResolutions = persistenceContext.getNaturalIdResolutions(); - final Object id = getIdentifier( entity, session ); - - // for reattachment of mutable natural-ids, we absolutely positively have to grab the snapshot from the - // database, because we have no other way to know if the state changed while detached. - final Object[] entitySnapshot = persistenceContext.getDatabaseSnapshot( id, this ); - final Object naturalIdSnapshot = naturalIdFromSnapshot( entitySnapshot ); - - naturalIdResolutions.removeSharedResolution( id, naturalIdSnapshot, this, false ); - final Object naturalId = naturalIdMapping.extractNaturalIdFromEntity( entity ); - naturalIdResolutions.manageLocalResolution( id, naturalId, this, CachedNaturalIdValueSource.UPDATE ); } private Object naturalIdFromSnapshot(Object[] entitySnapshot) { @@ -3960,7 +3934,8 @@ public boolean hasSubclasses() { @Override public boolean hasProxy() { // skip proxy instantiation if entity is bytecode enhanced - return entityMetamodel.isLazy() && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); + return entityMetamodel.isLazy() + && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading(); } @Override @Deprecated @@ -4045,7 +4020,7 @@ public Object createProxy(Object id, SharedSessionContractImplementor session) t @Override public String toString() { - return StringHelper.unqualify( getClass().getName() ) + return unqualify( getClass().getName() ) + '(' + entityMetamodel.getName() + ')'; } @@ -4251,11 +4226,10 @@ public Object getPropertyValue(Object object, String propertyName) { baseValueType = (ManagedMappingType) attributeMapping.getMappedType(); } } - else if ( identifierMapping instanceof NonAggregatedIdentifierMapping ) { - final AttributeMapping mapping = ( (NonAggregatedIdentifierMapping) identifierMapping ).findSubPart( - propertyName, - null - ).asAttributeMapping(); + else if ( identifierMapping instanceof NonAggregatedIdentifierMapping nonAggregatedIdentifierMapping ) { + final AttributeMapping mapping = + nonAggregatedIdentifierMapping.findSubPart( propertyName, null ) + .asAttributeMapping(); if ( mapping != null ) { baseValue = mapping.getValue( object ); if ( dotIndex != -1 ) { @@ -4394,12 +4368,13 @@ public Object[] getPropertyValuesToInsert( if ( shouldGetAllProperties( entity ) && accessOptimizer != null ) { return accessOptimizer.getPropertyValues( entity ); } - - final Object[] result = new Object[attributeMappings.size()]; - for ( int i = 0; i < attributeMappings.size(); i++ ) { - result[i] = getterCache[i].getForInsert( entity, mergeMap, session ); + else { + final Object[] result = new Object[attributeMappings.size()]; + for ( int i = 0; i < attributeMappings.size(); i++ ) { + result[i] = getterCache[i].getForInsert( entity, mergeMap, session ); + } + return result; } - return result; } protected boolean shouldGetAllProperties(Object entity) { @@ -4432,15 +4407,13 @@ protected List initInsertGeneratedProperties(List identifierList = isIdentifierAssignedByInsert() ? - List.of( getIdentifierMapping() ) : - Collections.emptyList(); - if ( originalSize > 0 && generatedBasicAttributes.size() == originalSize ) { - return Collections.unmodifiableList( combine( identifierList, generatedBasicAttributes ) ); - } - else { - return identifierList; - } + final List identifierList = + isIdentifierAssignedByInsert() + ? List.of( getIdentifierMapping() ) + : emptyList(); + return originalSize > 0 && generatedBasicAttributes.size() == originalSize + ? unmodifiableList( combine( identifierList, generatedBasicAttributes ) ) + : identifierList; } @Override @@ -4465,17 +4438,17 @@ protected List initUpdateGeneratedProperties(List generatedBasicAttributes = new ArrayList<>( originalSize ); for ( AttributeMapping generatedAttribute : generatedAttributes ) { - if ( generatedAttribute instanceof SelectableMapping - && ( (SelectableMapping) generatedAttribute ).getContainingTableExpression().equals( getSubclassTableName( 0 ) ) ) { + if ( generatedAttribute instanceof SelectableMapping selectableMapping + && selectableMapping.getContainingTableExpression().equals( getSubclassTableName( 0 ) ) ) { generatedBasicAttributes.add( generatedAttribute ); } } if ( generatedBasicAttributes.size() == originalSize ) { - return Collections.unmodifiableList( generatedBasicAttributes ); + return unmodifiableList( generatedBasicAttributes ); } else { - return Collections.emptyList(); + return emptyList(); } } @@ -4568,11 +4541,6 @@ public boolean hasNaturalIdentifier() { return entityMetamodel.hasNaturalIdentifier(); } - private void setPropertyValue(Object object, String propertyName, Object value) { - final AttributeMapping attributeMapping = findSubPart( propertyName, this ).asAttributeMapping(); - setterCache[attributeMapping.getStateArrayPosition()].set( object, value ); - } - public static int getTableId(String tableName, String[] tables) { for ( int j = 0; j < tables.length; j++ ) { if ( tableName.equalsIgnoreCase( tables[j] ) ) { @@ -6149,14 +6117,14 @@ private void internalInitSubclassPropertyAliasesMap(String path, List for (Property property : properties) { final String name = path == null ? property.getName() : path + "." + property.getName(); if ( property.isComposite() ) { - Component component = (Component) property.getValue(); + final Component component = (Component) property.getValue(); internalInitSubclassPropertyAliasesMap( name, component.getProperties() ); } - String[] aliases = new String[property.getColumnSpan()]; + final String[] aliases = new String[property.getColumnSpan()]; int l = 0; + final Dialect dialect = getDialect(); for ( Selectable selectable: property.getSelectables() ) { - Dialect dialect = getFactory().getJdbcServices().getDialect(); aliases[l] = selectable.getAlias( dialect, property.getValue().getTable() ); l++; } diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java index d91351296556..c35453ee5d9b 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/EntityPersister.java @@ -616,6 +616,22 @@ default Object load(Object id, Object optionalObject, LockOptions lockOptions, S return load( id, optionalObject, lockOptions, session ); } + /** + * Performs a load of multiple entities (of this type) by identifier simultaneously. + * + * @param ids The identifiers to load + * @param session The originating Session + * @param loadOptions The options for loading + * + * @return The loaded, matching entities + * + * @deprecated Use {@link #multiLoad(Object[], SharedSessionContractImplementor, MultiIdLoadOptions)} + */ + @Deprecated(since = "7") + default List multiLoad(Object[] ids, EventSource session, MultiIdLoadOptions loadOptions) { + return multiLoad( ids, (SharedSessionContractImplementor) session, loadOptions ); + } + /** * Performs a load of multiple entities (of this type) by identifier simultaneously. * @@ -625,7 +641,7 @@ default Object load(Object id, Object optionalObject, LockOptions lockOptions, S * * @return The loaded, matching entities */ - List multiLoad(Object[] ids, EventSource session, MultiIdLoadOptions loadOptions); + List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions); @Override default Object loadByUniqueKey(String propertyName, Object uniqueKey, SharedSessionContractImplementor session) { @@ -635,15 +651,35 @@ default Object loadByUniqueKey(String propertyName, Object uniqueKey, SharedSess ); } + /** + * Do a version check (optional operation) + * + * @deprecated Use {@link #lock(Object, Object, Object, LockMode, SharedSessionContractImplementor)} + */ + @Deprecated(since = "7") + default void lock(Object id, Object version, Object object, LockMode lockMode, EventSource session) { + lock( id, version, object, lockMode, (SharedSessionContractImplementor) session ); + } + /** * Do a version check (optional operation) */ - void lock(Object id, Object version, Object object, LockMode lockMode, EventSource session); + void lock(Object id, Object version, Object object, LockMode lockMode, SharedSessionContractImplementor session); + + /** + * Do a version check (optional operation) + * + * @deprecated Use {@link #lock(Object, Object, Object, LockOptions, SharedSessionContractImplementor)} + */ + @Deprecated(since = "7") + default void lock(Object id, Object version, Object object, LockOptions lockOptions, EventSource session) { + lock( id, version, object, lockOptions, (SharedSessionContractImplementor) session ); + } /** * Do a version check (optional operation) */ - void lock(Object id, Object version, Object object, LockOptions lockOptions, EventSource session); + void lock(Object id, Object version, Object object, LockOptions lockOptions, SharedSessionContractImplementor session); /** * Persist an instance diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java index f937b36fdb56..8608ecc1ff14 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java @@ -339,11 +339,6 @@ public String getAttributeMutationTableName(int i) { return getTableName();//ie. the subquery! yuck! } - @Override - public int getSubclassPropertyTableNumber(String propertyName) { - return 0; - } - @Override public String physicalTableNameForMutation(SelectableMapping selectableMapping) { assert !selectableMapping.isFormula(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/named/NamedObjectRepository.java b/hibernate-core/src/main/java/org/hibernate/query/named/NamedObjectRepository.java index 905411b696da..72839c59ab58 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/named/NamedObjectRepository.java +++ b/hibernate-core/src/main/java/org/hibernate/query/named/NamedObjectRepository.java @@ -84,7 +84,7 @@ public interface NamedObjectRepository { /** * Resolve the named query with the given name. */ - NamedQueryMemento resolve( + NamedQueryMemento resolve( SessionFactoryImplementor sessionFactory, MetadataImplementor bootMetamodel, String registrationName); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java index 3c5837b1df09..92e7dba62a25 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/internal/DeferredResultSetAccess.java @@ -7,13 +7,13 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.Collections; import org.hibernate.LockMode; import org.hibernate.LockOptions; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.pagination.LimitHandler; import org.hibernate.dialect.pagination.NoopLimitHandler; +import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlStatementLogger; import org.hibernate.engine.spi.SessionEventListenerManager; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -33,6 +33,8 @@ import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcSelectExecutor; +import static java.util.Collections.emptyMap; + /** * @author Steve Ebersole */ @@ -62,11 +64,13 @@ public DeferredResultSetAccess( JdbcSelectExecutor.StatementCreator statementCreator, int resultCountEstimate) { super( executionContext.getSession() ); + final JdbcServices jdbcServices = executionContext.getSession().getJdbcServices(); + this.jdbcParameterBindings = jdbcParameterBindings; this.executionContext = executionContext; this.jdbcSelect = jdbcSelect; this.statementCreator = statementCreator; - this.sqlStatementLogger = executionContext.getSession().getJdbcServices().getSqlStatementLogger(); + this.sqlStatementLogger = jdbcServices.getSqlStatementLogger(); this.resultCountEstimate = resultCountEstimate; final QueryOptions queryOptions = executionContext.getQueryOptions(); @@ -79,54 +83,61 @@ public DeferredResultSetAccess( else { // Note that limit and lock aren't set for SQM as that is applied during SQL rendering // But for native queries, we have to adapt the SQL string - final Dialect dialect = executionContext.getSession().getJdbcServices().getDialect(); - String sql; + final Dialect dialect = jdbcServices.getDialect(); + + final String sql = jdbcSelect.getSqlString(); + limit = queryOptions.getLimit(); - if ( limit == null || limit.isEmpty() || jdbcSelect.usesLimitParameters() ) { - sql = jdbcSelect.getSqlString(); - limitHandler = NoopLimitHandler.NO_LIMIT; - } - else { - limitHandler = dialect.getLimitHandler(); - sql = limitHandler.processSql( - jdbcSelect.getSqlString(), - limit, - queryOptions - ); - } + final boolean hasLimit = isHasLimit( jdbcSelect ); + limitHandler = hasLimit ? NoopLimitHandler.NO_LIMIT : dialect.getLimitHandler(); + final String sqlWithLimit = hasLimit ? sql : limitHandler.processSql( sql, limit, queryOptions ); final LockOptions lockOptions = queryOptions.getLockOptions(); final JdbcLockStrategy jdbcLockStrategy = jdbcSelect.getLockStrategy(); - if ( jdbcLockStrategy != JdbcLockStrategy.NONE - && lockOptions != null && !lockOptions.isEmpty() ) { - usesFollowOnLocking = useFollowOnLocking( jdbcLockStrategy, sql, queryOptions, lockOptions, dialect ); + final String sqlWithLocking; + if ( hasLocking( jdbcLockStrategy, lockOptions ) ) { + usesFollowOnLocking = useFollowOnLocking( jdbcLockStrategy, sqlWithLimit, queryOptions, lockOptions, dialect ); if ( usesFollowOnLocking ) { - final LockMode lockMode = determineFollowOnLockMode( lockOptions ); - if ( lockMode != LockMode.UPGRADE_SKIPLOCKED ) { - // Dialect prefers to perform locking in a separate step - if ( lockOptions.getLockMode() != LockMode.NONE ) { - LOG.usingFollowOnLocking(); - } - - final LockOptions lockOptionsToUse = new LockOptions( lockMode ); - lockOptionsToUse.setTimeOut( lockOptions.getTimeOut() ); - lockOptionsToUse.setLockScope( lockOptions.getLockScope() ); - - registerAfterLoadAction( executionContext, lockOptionsToUse ); - } + handleFollowOnLocking( executionContext, lockOptions ); + sqlWithLocking = sqlWithLimit; } else { - sql = dialect.applyLocksToSql( sql, lockOptions, Collections.emptyMap() ); + sqlWithLocking = dialect.applyLocksToSql( sqlWithLimit, lockOptions, emptyMap() ); } } else { usesFollowOnLocking = false; + sqlWithLocking = sqlWithLimit; } - finalSql = dialect.addSqlHintOrComment( - sql, - queryOptions, - executionContext.getSession().getFactory().getSessionFactoryOptions().isCommentsEnabled() - ); + + final boolean commentsEnabled = + executionContext.getSession().getFactory() + .getSessionFactoryOptions().isCommentsEnabled(); + finalSql = dialect.addSqlHintOrComment( sqlWithLocking, queryOptions, commentsEnabled ); + } + } + + private boolean isHasLimit(JdbcOperationQuerySelect jdbcSelect) { + return limit == null || limit.isEmpty() || jdbcSelect.usesLimitParameters(); + } + + private static boolean hasLocking(JdbcLockStrategy jdbcLockStrategy, LockOptions lockOptions) { + return jdbcLockStrategy != JdbcLockStrategy.NONE && lockOptions != null && !lockOptions.isEmpty(); + } + + private void handleFollowOnLocking(ExecutionContext executionContext, LockOptions lockOptions) { + final LockMode lockMode = determineFollowOnLockMode( lockOptions ); + if ( lockMode != LockMode.UPGRADE_SKIPLOCKED ) { + // Dialect prefers to perform locking in a separate step + if ( lockOptions.getLockMode() != LockMode.NONE ) { + LOG.usingFollowOnLocking(); + } + + final LockOptions lockOptionsToUse = new LockOptions( lockMode ); + lockOptionsToUse.setTimeOut( lockOptions.getTimeOut() ); + lockOptionsToUse.setLockScope( lockOptions.getLockScope() ); + + registerAfterLoadAction( executionContext, lockOptionsToUse ); } } @@ -134,13 +145,9 @@ public DeferredResultSetAccess( * For Hibernate Reactive */ protected void registerAfterLoadAction(ExecutionContext executionContext, LockOptions lockOptionsToUse) { - executionContext.getCallback().registerAfterLoadAction( (entity, persister, session) -> - session.asSessionImplementor().lock( - persister.getEntityName(), - entity, - lockOptionsToUse - ) - ); + executionContext.getCallback() + .registerAfterLoadAction( (entity, persister, session) -> + session.lock( persister.getEntityName(), entity, lockOptionsToUse ) ); } private static boolean useFollowOnLocking( @@ -188,20 +195,10 @@ public boolean usesFollowOnLocking() { } protected void bindParameters(PreparedStatement preparedStatement) throws SQLException { - final QueryOptions queryOptions = executionContext.getQueryOptions(); - - // set options - if ( queryOptions != null ) { - if ( queryOptions.getFetchSize() != null ) { - preparedStatement.setFetchSize( queryOptions.getFetchSize() ); - } - if ( queryOptions.getTimeout() != null ) { - preparedStatement.setQueryTimeout( queryOptions.getTimeout() ); - } - } + setQueryOptions( preparedStatement ); // bind parameters - // todo : validate that all query parameters were bound? + // todo : validate that all query parameters were bound? int paramBindingPosition = 1; paramBindingPosition += limitHandler.bindLimitParametersAtStartOfQuery( limit, preparedStatement, paramBindingPosition ); for ( JdbcParameterBinder parameterBinder : jdbcSelect.getParameterBinders() ) { @@ -212,7 +209,6 @@ protected void bindParameters(PreparedStatement preparedStatement) throws SQLExc executionContext ); } - paramBindingPosition += limitHandler.bindLimitParametersAtEndOfQuery( limit, preparedStatement, paramBindingPosition ); if ( !jdbcSelect.usesLimitParameters() && limit != null && limit.getMaxRows() != null ) { @@ -226,6 +222,19 @@ protected void bindParameters(PreparedStatement preparedStatement) throws SQLExc } } + private void setQueryOptions(PreparedStatement preparedStatement) throws SQLException { + final QueryOptions queryOptions = executionContext.getQueryOptions(); + // set options + if ( queryOptions != null ) { + if ( queryOptions.getFetchSize() != null ) { + preparedStatement.setFetchSize( queryOptions.getFetchSize() ); + } + if ( queryOptions.getTimeout() != null ) { + preparedStatement.setQueryTimeout( queryOptions.getTimeout() ); + } + } + } + private void executeQuery() { final LogicalConnectionImplementor logicalConnection = getPersistenceContext().getJdbcCoordinator().getLogicalConnection(); @@ -238,9 +247,7 @@ private void executeQuery() { bindParameters( preparedStatement ); - final SessionEventListenerManager eventListenerManager = session - .getEventListenerManager(); - + final SessionEventListenerManager eventListenerManager = session.getEventListenerManager(); long executeStartNanos = 0; if ( sqlStatementLogger.getLogSlowQuery() > 0 ) { executeStartNanos = System.nanoTime(); @@ -261,17 +268,15 @@ private void executeQuery() { skipRows( resultSet ); logicalConnection.getResourceRegistry().register( resultSet, preparedStatement ); } - catch (SQLException e) { + catch (SQLException exception) { try { release(); } - catch (RuntimeException e2) { - e.addSuppressed( e2 ); + catch (RuntimeException suppressed) { + exception.addSuppressed( suppressed ); } - throw session.getJdbcServices().getSqlExceptionHelper().convert( - e, - "JDBC exception executing SQL [" + finalSql + "]" - ); + throw session.getJdbcServices().getSqlExceptionHelper() + .convert( exception, "JDBC exception executing SQL [" + finalSql + "]" ); } } @@ -281,13 +286,7 @@ private JdbcSessionContext context() { protected void skipRows(ResultSet resultSet) throws SQLException { // For dialects that don't support an offset clause - final int rowsToSkip; - if ( !jdbcSelect.usesLimitParameters() && limit != null && limit.getFirstRow() != null && !limitHandler.supportsLimitOffset() ) { - rowsToSkip = limit.getFirstRow(); - } - else { - rowsToSkip = jdbcSelect.getRowsToSkip(); - } + final int rowsToSkip = getRowsToSkip(); if ( rowsToSkip != 0 ) { try { resultSet.absolute( rowsToSkip ); @@ -307,13 +306,20 @@ protected void skipRows(ResultSet resultSet) throws SQLException { } } + private int getRowsToSkip() { + return !jdbcSelect.usesLimitParameters() + && limit != null && limit.getFirstRow() != null + && !limitHandler.supportsLimitOffset() + ? limit.getFirstRow() + : jdbcSelect.getRowsToSkip(); + } + protected ResultSet wrapResultSet(ResultSet resultSet) throws SQLException { return resultSet; } protected LockMode determineFollowOnLockMode(LockOptions lockOptions) { final LockMode lockModeToUse = lockOptions.findGreatestLockMode(); - if ( lockOptions.hasAliasSpecificLockModes() ) { if ( lockOptions.getLockMode() == LockMode.NONE && lockModeToUse == LockMode.NONE ) { return lockModeToUse; @@ -327,8 +333,8 @@ protected LockMode determineFollowOnLockMode(LockOptions lockOptions) { @Override public void release() { - final LogicalConnectionImplementor logicalConnection = getPersistenceContext().getJdbcCoordinator() - .getLogicalConnection(); + final LogicalConnectionImplementor logicalConnection = + getPersistenceContext().getJdbcCoordinator().getLogicalConnection(); if ( resultSet != null ) { logicalConnection.getResourceRegistry().release( resultSet, preparedStatement ); resultSet = null; @@ -347,12 +353,14 @@ public int getResultCountEstimate() { if ( limit != null && limit.getMaxRows() != null ) { return limit.getMaxRows(); } - if ( jdbcSelect.getLimitParameter() != null ) { + else if ( jdbcSelect.getLimitParameter() != null ) { return (int) jdbcParameterBindings.getBinding( jdbcSelect.getLimitParameter() ).getBindValue(); } - if ( resultCountEstimate > 0 ) { + else if ( resultCountEstimate > 0 ) { return resultCountEstimate; } - return super.getResultCountEstimate(); + else { + return super.getResultCountEstimate(); + } } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java index 004e87ec2e13..142df17c186c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/cfg/persister/GoofyPersisterClassProvider.java @@ -34,7 +34,6 @@ import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.generator.values.GeneratedValuesMutationDelegate; import org.hibernate.id.IdentifierGenerator; @@ -402,16 +401,16 @@ public Object load(Object id, Object optionalObject, LockOptions lockOptions, Sh } @Override - public List multiLoad(Object[] ids, EventSource session, MultiIdLoadOptions loadOptions) { + public List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) { return Collections.emptyList(); } @Override - public void lock(Object id, Object version, Object object, LockMode lockMode, EventSource session) { + public void lock(Object id, Object version, Object object, LockMode lockMode, SharedSessionContractImplementor session) { } @Override - public void lock(Object id, Object version, Object object, LockOptions lockOptions, EventSource session) { + public void lock(Object id, Object version, Object object, LockOptions lockOptions, SharedSessionContractImplementor session) { } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java index 651d46565422..7eac16cb6a3c 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jpa/ejb3configuration/PersisterClassProviderTest.java @@ -31,7 +31,6 @@ import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.generator.values.GeneratedValues; import org.hibernate.generator.values.GeneratedValuesMutationDelegate; import org.hibernate.id.IdentifierGenerator; @@ -441,16 +440,16 @@ public Object load(Object id, Object optionalObject, LockOptions lockOptions, Sh } @Override - public List multiLoad(Object[] ids, EventSource session, MultiIdLoadOptions loadOptions) { + public List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) { return Collections.emptyList(); } @Override - public void lock(Object id, Object version, Object object, LockMode lockMode, EventSource session) { + public void lock(Object id, Object version, Object object, LockMode lockMode, SharedSessionContractImplementor session) { } @Override - public void lock(Object id, Object version, Object object, LockOptions lockOptions, EventSource session) { + public void lock(Object id, Object version, Object object, LockOptions lockOptions, SharedSessionContractImplementor session) { } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java index 196bb5770b46..3467157f651a 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/legacy/CustomPersister.java @@ -431,7 +431,7 @@ public Object load( } @Override - public List multiLoad(Object[] ids, EventSource session, MultiIdLoadOptions loadOptions) { + public List multiLoad(Object[] ids, SharedSessionContractImplementor session, MultiIdLoadOptions loadOptions) { return Collections.emptyList(); } @@ -489,7 +489,7 @@ public void lock( Object version, Object object, LockOptions lockOptions, - EventSource session + SharedSessionContractImplementor session ) throws HibernateException { throw new UnsupportedOperationException(); @@ -503,7 +503,7 @@ public void lock( Object version, Object object, LockMode lockMode, - EventSource session + SharedSessionContractImplementor session ) throws HibernateException { throw new UnsupportedOperationException(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/GetMultipleTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/GetMultipleTest.java index 95c8c57315f1..f72f9acac3b2 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/GetMultipleTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/stateless/GetMultipleTest.java @@ -13,6 +13,7 @@ import java.util.List; +import static org.hibernate.LockMode.PESSIMISTIC_READ; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -36,6 +37,13 @@ public class GetMultipleTest { assertEquals("hello mars",all.get(2).message); assertNull(all.get(1)); }); + scope.inStatelessTransaction(s-> { + List all = s.getMultiple(Record.class, List.of(456L, 123L, 2L), PESSIMISTIC_READ); + assertEquals("hello mars",all.get(0).message); + assertEquals("hello earth",all.get(1).message); + assertNull(all.get(2)); + }); + } @Entity static class Record {