diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java index 88b3eae4d299..84831c176e26 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/SessionFactoryImplementor.java @@ -34,6 +34,8 @@ import org.hibernate.resource.beans.spi.ManagedBeanRegistry; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.sql.ast.spi.ParameterMarkerStrategy; +import org.hibernate.sql.exec.internal.JdbcSelectWithActions; +import org.hibernate.sql.exec.spi.JdbcSelectWithActionsBuilder; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducerProvider; import org.hibernate.stat.spi.StatisticsImplementor; import org.hibernate.generator.Generator; @@ -315,4 +317,9 @@ default RootGraphImplementor createEntityGraph(Class entityType) { */ String bestGuessEntityName(Object object); + @Incubating + default JdbcSelectWithActionsBuilder getJdbcSelectWithActionsBuilder(){ + return new JdbcSelectWithActions.Builder(); + } + } 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 396f98b972a2..34a3a651b864 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 @@ -2201,7 +2201,8 @@ protected LockingStrategy generateLocker(LockMode lockMode, Locking.Scope lockSc return getDialect().getLockingStrategy( this, lockMode, lockScope ); } - private LockingStrategy getLocker(LockMode lockMode, Locking.Scope lockScope) { + // Used by Hibernate Reactive + protected LockingStrategy getLocker(LockMode lockMode, Locking.Scope lockScope) { return lockScope != Locking.Scope.ROOT_ONLY // be sure to not use the cached form if any form of extended locking is requested ? generateLocker( lockMode, lockScope ) diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java index 73ab28372b9b..5a385432cf3b 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java @@ -14,7 +14,6 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.DmlTargetColumnQualifierSupport; import org.hibernate.dialect.SelectItemReferenceStrategy; -import org.hibernate.dialect.lock.spi.LockTimeoutType; import org.hibernate.dialect.lock.spi.LockingSupport; import org.hibernate.engine.jdbc.Size; import org.hibernate.engine.jdbc.spi.JdbcServices; @@ -177,11 +176,7 @@ import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect; import org.hibernate.sql.exec.internal.JdbcOperationQueryUpdate; import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; -import org.hibernate.sql.exec.internal.JdbcSelectWithActions; -import org.hibernate.sql.exec.internal.LockTimeoutHandler; import org.hibernate.sql.exec.internal.SqlTypedMappingJdbcParameter; -import org.hibernate.sql.exec.internal.lock.CollectionLockingAction; -import org.hibernate.sql.exec.internal.lock.FollowOnLockingAction; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcLockStrategy; import org.hibernate.sql.exec.spi.JdbcOperation; @@ -893,26 +888,17 @@ protected JdbcSelect translateSelect(SelectStatement selectStatement) { final LockingSupport lockingSupport = getDialect().getLockingSupport(); final LockingSupport.Metadata lockingSupportMetadata = lockingSupport.getMetadata(); - - final JdbcSelectWithActions.Builder builder = new JdbcSelectWithActions.Builder( jdbcSelect ); - - final LockTimeoutType lockTimeoutType = lockingSupportMetadata.getLockTimeoutType( lockOptions.getTimeout() ); - if ( lockTimeoutType == LockTimeoutType.CONNECTION ) { - builder.addSecondaryActionPair( new LockTimeoutHandler( - lockOptions.getTimeout(), - lockingSupport.getConnectionLockTimeoutStrategy() - ) ); - } - final LockStrategy lockStrategy = determineLockingStrategy( lockingTarget, lockOptions.getFollowOnStrategy() ); - if ( lockStrategy == LockStrategy.FOLLOW_ON ) { - FollowOnLockingAction.apply( lockOptions, lockingTarget, lockingClauseStrategy, builder ); - } - else if ( lockOptions.getScope() == Locking.Scope.INCLUDE_COLLECTIONS ) { - CollectionLockingAction.apply( lockOptions, lockingTarget, builder ); - } - return builder.build(); + return getSessionFactory().getJdbcSelectWithActionsBuilder() + .setPrimaryAction( jdbcSelect ) + .setLockTimeoutType( lockingSupportMetadata.getLockTimeoutType( lockOptions.getTimeout() ) ) + .setLockingSupport( lockingSupport ) + .setLockOptions( lockOptions ) + .setLockingTarget( lockingTarget ) + .setLockingClauseStrategy( lockingClauseStrategy ) + .setIsFollowOnLockStrategy( lockStrategy == LockStrategy.FOLLOW_ON ) + .build(); } private JdbcValuesMappingProducer buildJdbcValuesMappingProducer(SelectStatement selectStatement) { diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectWithActions.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectWithActions.java index fb8f856ef1bb..a2d9da1d3e70 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectWithActions.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/JdbcSelectWithActions.java @@ -5,9 +5,17 @@ package org.hibernate.sql.exec.internal; import org.checkerframework.checker.nullness.qual.Nullable; +import org.hibernate.LockOptions; +import org.hibernate.Locking; +import org.hibernate.dialect.lock.spi.LockTimeoutType; +import org.hibernate.dialect.lock.spi.LockingSupport; import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.query.spi.QueryOptions; +import org.hibernate.sql.ast.spi.LockingClauseStrategy; import org.hibernate.sql.ast.tree.expression.JdbcParameter; +import org.hibernate.sql.ast.tree.select.QuerySpec; +import org.hibernate.sql.exec.internal.lock.CollectionLockingAction; +import org.hibernate.sql.exec.internal.lock.FollowOnLockingAction; import org.hibernate.sql.exec.spi.ExecutionContext; import org.hibernate.sql.exec.spi.JdbcLockStrategy; import org.hibernate.sql.exec.spi.JdbcOperationQuery; @@ -15,6 +23,7 @@ import org.hibernate.sql.exec.spi.JdbcParameterBinding; import org.hibernate.sql.exec.spi.JdbcParameterBindings; import org.hibernate.sql.exec.spi.JdbcSelect; +import org.hibernate.sql.exec.spi.JdbcSelectWithActionsBuilder; import org.hibernate.sql.exec.spi.LoadedValuesCollector; import org.hibernate.sql.exec.spi.PostAction; import org.hibernate.sql.exec.spi.PreAction; @@ -35,9 +44,12 @@ public class JdbcSelectWithActions implements JdbcOperationQuery, JdbcSelect { private final JdbcOperationQuerySelect primaryOperation; - private final LoadedValuesCollector loadedValuesCollector; - private final PreAction[] preActions; - private final PostAction[] postActions; + // Used by Hibernate Reactive + protected final LoadedValuesCollector loadedValuesCollector; + // Used by Hibernate Reactive + protected final PreAction[] preActions; + // Used by Hibernate Reactive + protected final PostAction[] postActions; public JdbcSelectWithActions( JdbcOperationQuerySelect primaryOperation, @@ -149,24 +161,84 @@ public boolean isCompatibleWith(JdbcParameterBindings jdbcParameterBindings, Que return primaryOperation.isCompatibleWith( jdbcParameterBindings, queryOptions ); } - public static class Builder { - private final JdbcOperationQuerySelect primaryAction; - + public static class Builder implements JdbcSelectWithActionsBuilder { + private JdbcOperationQuerySelect primaryAction; private LoadedValuesCollector loadedValuesCollector; protected List preActions; protected List postActions; - - public Builder(JdbcOperationQuerySelect primaryAction) { - this.primaryAction = primaryAction; + protected LockTimeoutType lockTimeoutType; + protected LockingSupport lockingSupport; + protected LockOptions lockOptions; + protected QuerySpec lockingTarget; + protected LockingClauseStrategy lockingClauseStrategy; + boolean isFollonOnLockStrategy; + + @Override + public Builder setPrimaryAction(JdbcSelect primaryAction){ + assert primaryAction instanceof JdbcOperationQuerySelect; + this.primaryAction = (JdbcOperationQuerySelect) primaryAction; + return this; } @SuppressWarnings("UnusedReturnValue") + @Override public Builder setLoadedValuesCollector(LoadedValuesCollector loadedValuesCollector) { this.loadedValuesCollector = loadedValuesCollector; return this; } + @Override + public Builder setLockTimeoutType(LockTimeoutType lockTimeoutType){ + this.lockTimeoutType = lockTimeoutType; + return this; + } + + @Override + public Builder setLockingSupport(LockingSupport lockingSupport){ + this.lockingSupport = lockingSupport; + return this; + } + + @Override + public Builder setLockOptions(LockOptions lockOptions){ + this.lockOptions = lockOptions; + return this; + } + + @Override + public Builder setLockingTarget(QuerySpec lockingTarget){ + this.lockingTarget = lockingTarget; + return this; + } + + @Override + public Builder setLockingClauseStrategy(LockingClauseStrategy lockingClauseStrategy){ + this.lockingClauseStrategy = lockingClauseStrategy; + return this; + } + + @Override + public Builder setIsFollowOnLockStrategy(boolean isFollonOnLockStrategy){ + this.isFollonOnLockStrategy = isFollonOnLockStrategy; + return this; + } + + @Override public JdbcSelect build() { + if ( lockTimeoutType == LockTimeoutType.CONNECTION ) { + addSecondaryActionPair( + new LockTimeoutHandler( + lockOptions.getTimeout(), + lockingSupport.getConnectionLockTimeoutStrategy() + ) + ); + } + if ( isFollonOnLockStrategy ) { + FollowOnLockingAction.apply( lockOptions, lockingTarget, lockingClauseStrategy, this ); + } + else if ( lockOptions.getScope() == Locking.Scope.INCLUDE_COLLECTIONS ) { + CollectionLockingAction.apply( lockOptions, lockingTarget, this ); + } if ( preActions == null && postActions == null ) { assert loadedValuesCollector == null; return primaryAction; @@ -182,6 +254,7 @@ public JdbcSelect build() { * * @return {@code this}, for method chaining. */ + @Override public Builder appendPreAction(PreAction... actions) { if ( preActions == null ) { preActions = new ArrayList<>(); @@ -195,6 +268,7 @@ public Builder appendPreAction(PreAction... actions) { * * @return {@code this}, for method chaining. */ + @Override public Builder prependPreAction(PreAction... actions) { if ( preActions == null ) { preActions = new ArrayList<>(); @@ -209,6 +283,7 @@ public Builder prependPreAction(PreAction... actions) { * * @return {@code this}, for method chaining. */ + @Override public Builder appendPostAction(PostAction... actions) { if ( postActions == null ) { postActions = new ArrayList<>(); @@ -222,6 +297,7 @@ public Builder appendPostAction(PostAction... actions) { * * @return {@code this}, for method chaining. */ + @Override public Builder prependPostAction(PostAction... actions) { if ( postActions == null ) { postActions = new ArrayList<>(); @@ -243,6 +319,7 @@ public Builder prependPostAction(PostAction... actions) { * * @return {@code this}, for method chaining. */ + @Override public Builder addSecondaryActionPair(SecondaryAction action) { return addSecondaryActionPair( (PreAction) action, (PostAction) action ); } @@ -255,24 +332,27 @@ public Builder addSecondaryActionPair(SecondaryAction action) { * * @return {@code this}, for method chaining. */ + @Override public Builder addSecondaryActionPair(PreAction preAction, PostAction postAction) { prependPreAction( preAction ); appendPostAction( postAction ); return this; } - private static PreAction[] toPreActionArray(List actions) { - if ( CollectionHelper.isEmpty( actions ) ) { - return null; - } - return actions.toArray( new PreAction[0] ); + // Used by Hibernate Reactive + static PreAction[] toPreActionArray(List actions) { + if ( CollectionHelper.isEmpty( actions ) ) { + return null; } - - private static PostAction[] toPostActionArray(List actions) { - if ( CollectionHelper.isEmpty( actions ) ) { - return null; - } - return actions.toArray( new PostAction[0] ); + return actions.toArray( new PreAction[0] ); + } + // Used by Hibernate Reactive + static PostAction[] toPostActionArray(List actions) { + if ( CollectionHelper.isEmpty( actions ) ) { + return null; } + return actions.toArray( new PostAction[0] ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/CollectionLockingAction.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/CollectionLockingAction.java index 56457df3fa98..8322a0533f1c 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/CollectionLockingAction.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/CollectionLockingAction.java @@ -18,8 +18,8 @@ import org.hibernate.sql.ast.tree.from.FromClause; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.exec.internal.JdbcSelectWithActions; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcSelectWithActionsBuilder; import org.hibernate.sql.exec.spi.LoadedValuesCollector; import org.hibernate.sql.exec.spi.PostAction; import org.hibernate.sql.exec.spi.StatementAccess; @@ -40,11 +40,15 @@ * @author Steve Ebersole */ public class CollectionLockingAction implements PostAction { - private final LoadedValuesCollectorImpl loadedValuesCollector; - private final LockMode lockMode; - private final Timeout lockTimeout; - - private CollectionLockingAction( + // Used by Hibernate Reactive + protected final LoadedValuesCollectorImpl loadedValuesCollector; + // Used by Hibernate Reactive + protected final LockMode lockMode; + // Used by Hibernate Reactive + protected final Timeout lockTimeout; + + // Used by Hibernate Reactive + protected CollectionLockingAction( LoadedValuesCollectorImpl loadedValuesCollector, LockMode lockMode, Timeout lockTimeout) { @@ -56,7 +60,7 @@ private CollectionLockingAction( public static void apply( LockOptions lockOptions, QuerySpec lockingTarget, - JdbcSelectWithActions.Builder jdbcSelectBuilder) { + JdbcSelectWithActionsBuilder jdbcSelectBuilder) { assert lockOptions.getScope() == Locking.Scope.INCLUDE_COLLECTIONS; final var loadedValuesCollector = resolveLoadedValuesCollector( lockingTarget.getFromClause() ); @@ -78,6 +82,11 @@ public void performPostAction( StatementAccess jdbcStatementAccess, Connection jdbcConnection, ExecutionContext executionContext) { + performPostAction( executionContext ); + } + + // Used by Hibernate Reactive + protected void performPostAction(ExecutionContext executionContext) { LockingHelper.logLoadedValues( loadedValuesCollector ); final var session = executionContext.getSession(); @@ -130,7 +139,8 @@ public void performPostAction( } } - private static LoadedValuesCollectorImpl resolveLoadedValuesCollector(FromClause fromClause) { + // Used by Hibernate Reactive + protected static LoadedValuesCollectorImpl resolveLoadedValuesCollector(FromClause fromClause) { final var fromClauseRoots = fromClause.getRoots(); if ( fromClauseRoots.size() == 1 ) { return new LoadedValuesCollectorImpl( @@ -144,7 +154,8 @@ private static LoadedValuesCollectorImpl resolveLoadedValuesCollector(FromClause } } - private static Map> segmentLoadedValues(LoadedValuesCollector loadedValuesCollector) { + // Used by Hibernate Reactive + protected static Map> segmentLoadedValues(LoadedValuesCollector loadedValuesCollector) { final Map> map = new IdentityHashMap<>(); LockingHelper.segmentLoadedValues( loadedValuesCollector.getCollectedRootEntities(), map ); LockingHelper.segmentLoadedValues( loadedValuesCollector.getCollectedNonRootEntities(), map ); @@ -155,7 +166,8 @@ private static Map> segmentLoadedValues(Loade return map; } - private static class LoadedValuesCollectorImpl implements LoadedValuesCollector { + // Used by Hibernate Reactive + protected static class LoadedValuesCollectorImpl implements LoadedValuesCollector { private final List rootPaths; private List rootEntitiesToLock; diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/FollowOnLockingAction.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/FollowOnLockingAction.java index c22b76214459..e9ef709d94f9 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/FollowOnLockingAction.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/FollowOnLockingAction.java @@ -10,6 +10,7 @@ import org.hibernate.LockOptions; import org.hibernate.Locking; import org.hibernate.engine.spi.CollectionKey; +import org.hibernate.engine.spi.EffectiveEntityGraph; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.graph.GraphSemantic; @@ -26,8 +27,8 @@ import org.hibernate.sql.ast.tree.from.FromClause; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.ast.tree.select.QuerySpec; -import org.hibernate.sql.exec.internal.JdbcSelectWithActions; import org.hibernate.sql.exec.spi.ExecutionContext; +import org.hibernate.sql.exec.spi.JdbcSelectWithActionsBuilder; import org.hibernate.sql.exec.spi.StatementAccess; import org.hibernate.sql.exec.spi.LoadedValuesCollector; import org.hibernate.sql.exec.spi.PostAction; @@ -54,12 +55,15 @@ * @author Steve Ebersole */ public class FollowOnLockingAction implements PostAction { - private final LoadedValuesCollectorImpl loadedValuesCollector; + // Used by Hibernate Reactive + protected final LoadedValuesCollectorImpl loadedValuesCollector; private final LockMode lockMode; private final Timeout lockTimeout; - private final Locking.Scope lockScope; + // Used by Hibernate Reactive + protected final Locking.Scope lockScope; - private FollowOnLockingAction( + // Used by Hibernate Reactive + protected FollowOnLockingAction( LoadedValuesCollectorImpl loadedValuesCollector, LockMode lockMode, Timeout lockTimeout, @@ -74,7 +78,7 @@ public static void apply( LockOptions lockOptions, QuerySpec lockingTarget, LockingClauseStrategy lockingClauseStrategy, - JdbcSelectWithActions.Builder jdbcSelectBuilder) { + JdbcSelectWithActionsBuilder jdbcSelectBuilder) { final var fromClause = lockingTarget.getFromClause(); final var loadedValuesCollector = resolveLoadedValuesCollector( fromClause, lockingClauseStrategy ); @@ -118,16 +122,6 @@ public void performPostAction( // we match each attribute to the table it is mapped to and add it to // the select-list for that table-segment. entitySegments.forEach( (entityMappingType, entityKeys) -> { - if ( SQL_EXEC_LOGGER.isDebugEnabled() ) { - SQL_EXEC_LOGGER.startingFollowOnLockingProcess( entityMappingType.getEntityName() ); - } - - // apply an empty "fetch graph" to make sure any embedded associations reachable from - // any of the DomainResults we will create are treated as lazy - final var graph = entityMappingType.createRootGraph( session ); - effectiveEntityGraph.clear(); - effectiveEntityGraph.applyGraph( graph, GraphSemantic.FETCH ); - // create a table-lock reference for each table for the entity (keyed by name) final var tableLocks = prepareTableLocks( entityMappingType, entityKeys, session ); @@ -135,57 +129,17 @@ public void performPostAction( // we'll use this later when we adjust the state array and inject state into the entity instance. final var entityDetailsMap = LockingHelper.resolveEntityKeys( entityKeys, executionContext ); - entityMappingType.forEachAttributeMapping( (index, attributeMapping) -> { - // we need to handle collections specially (which we do below, so skip them here) - if ( !(attributeMapping instanceof PluralAttributeMapping) ) { - final var tableLock = resolveTableLock( attributeMapping, tableLocks, entityMappingType ); - if ( tableLock == null ) { - throw new AssertionFailure( String.format( - Locale.ROOT, - "Unable to locate table for attribute `%s`", - attributeMapping.getNavigableRole().getFullPath() - ) ); - } - - // here we apply the selection for the attribute to the corresponding table-lock ref - tableLock.applyAttribute( index, attributeMapping ); - } - } ); - - // now we do process any collections, if asked - if ( lockScope == Locking.Scope.INCLUDE_COLLECTIONS ) { - SqmMutationStrategyHelper.visitCollectionTables( entityMappingType, (attribute) -> { - // we may need to lock the "collection table". - // the conditions are a bit unclear as to directionality, etc., so for now lock each. - LockingHelper.lockCollectionTable( - attribute, - lockMode, - lockTimeout, - entityDetailsMap, - executionContext - ); - } ); - } - else if ( lockScope == Locking.Scope.INCLUDE_FETCHES - && loadedValuesCollector.getCollectedCollections() != null - && !loadedValuesCollector.getCollectedCollections().isEmpty() ) { - final var attributeKeys = collectionSegments.get( entityMappingType ); - if ( attributeKeys != null ) { - for ( var entry : attributeKeys.entrySet() ) { - LockingHelper.lockCollectionTable( - entry.getKey(), - lockMode, - lockTimeout, - entry.getValue(), - executionContext - ); - } - } - } - - // at this point, we have all the individual locking selects ready to go - execute them - final var lockingOptions = buildLockingOptions( executionContext ); + final var lockingOptions = buildLockingOptions( + tableLocks, + entityDetailsMap, + entityMappingType, + effectiveEntityGraph, + entityKeys, + collectionSegments, + session, + executionContext ); + tableLocks.forEach( (s, tableLock) -> tableLock.performActions( entityDetailsMap, lockingOptions, session ) ); } ); @@ -197,6 +151,78 @@ else if ( lockScope == Locking.Scope.INCLUDE_FETCHES } } + // Used by Hibernate Reactive + public QueryOptions buildLockingOptions( + Map tableLocks, + Map entityDetailsMap, + EntityMappingType entityMappingType, + EffectiveEntityGraph effectiveEntityGraph, + List entityKeys, + Map>> collectionSegments, + SharedSessionContractImplementor session, + ExecutionContext executionContext) { + if ( SQL_EXEC_LOGGER.isDebugEnabled() ) { + SQL_EXEC_LOGGER.startingFollowOnLockingProcess( entityMappingType.getEntityName() ); + } + + // apply an empty "fetch graph" to make sure any embedded associations reachable from + // any of the DomainResults we will create are treated as lazy + final var graph = entityMappingType.createRootGraph( session ); + effectiveEntityGraph.clear(); + effectiveEntityGraph.applyGraph( graph, GraphSemantic.FETCH ); + + entityMappingType.forEachAttributeMapping( (index, attributeMapping) -> { + // we need to handle collections specially (which we do below, so skip them here) + if ( !(attributeMapping instanceof PluralAttributeMapping) ) { + final var tableLock = resolveTableLock( attributeMapping, tableLocks, entityMappingType ); + if ( tableLock == null ) { + throw new AssertionFailure( String.format( + Locale.ROOT, + "Unable to locate table for attribute `%s`", + attributeMapping.getNavigableRole().getFullPath() + ) ); + } + + // here we apply the selection for the attribute to the corresponding table-lock ref + tableLock.applyAttribute( index, attributeMapping ); + } + } ); + + // now we do process any collections, if asked + if ( lockScope == Locking.Scope.INCLUDE_COLLECTIONS ) { + SqmMutationStrategyHelper.visitCollectionTables( entityMappingType, (attribute) -> { + // we may need to lock the "collection table". + // the conditions are a bit unclear as to directionality, etc., so for now lock each. + LockingHelper.lockCollectionTable( + attribute, + lockMode, + lockTimeout, + entityDetailsMap, + executionContext + ); + } ); + } + else if ( lockScope == Locking.Scope.INCLUDE_FETCHES + && loadedValuesCollector.getCollectedCollections() != null + && !loadedValuesCollector.getCollectedCollections().isEmpty() ) { + final var attributeKeys = collectionSegments.get( entityMappingType ); + if ( attributeKeys != null ) { + for ( var entry : attributeKeys.entrySet() ) { + LockingHelper.lockCollectionTable( + entry.getKey(), + lockMode, + lockTimeout, + entry.getValue(), + executionContext + ); + } + } + } + + // at this point, we have all the individual locking selects ready to go - execute them + return buildLockingOptions( executionContext ); + } + private TableLock resolveTableLock( AttributeMapping attributeMapping, Map tableSegments, @@ -223,7 +249,8 @@ private QueryOptions buildLockingOptions(ExecutionContext executionContext) { return lockingQueryOptions; } - private Map> segmentLoadedValues() { + // Used by Hibernate Reactive + protected Map> segmentLoadedValues() { final Map> map = new IdentityHashMap<>(); LockingHelper.segmentLoadedValues( loadedValuesCollector.getCollectedRootEntities(), map ); LockingHelper.segmentLoadedValues( loadedValuesCollector.getCollectedNonRootEntities(), map ); @@ -233,13 +260,15 @@ private Map> segmentLoadedValues() { return map; } - private Map>> segmentLoadedCollections() { + // Used by Hibernate Reactive + protected Map>> segmentLoadedCollections() { final Map>> map = new HashMap<>(); LockingHelper.segmentLoadedCollections( loadedValuesCollector.getCollectedCollections(), map ); return map; } - private Map prepareTableLocks( + // Used by Hibernate Reactive + protected Map prepareTableLocks( EntityMappingType entityMappingType, List entityKeys, SharedSessionContractImplementor session) { @@ -251,11 +280,13 @@ private Map prepareTableLocks( return segments; } - private TableLock createTableLock(TableDetails tableDetails, EntityMappingType entityMappingType, List entityKeys, SharedSessionContractImplementor session) { + // Used by Hibernate Reactive + protected TableLock createTableLock(TableDetails tableDetails, EntityMappingType entityMappingType, List entityKeys, SharedSessionContractImplementor session) { return new TableLock( tableDetails, entityMappingType, entityKeys, session ); } - private static LoadedValuesCollectorImpl resolveLoadedValuesCollector( + // Used by Hibernate Reactive + protected static LoadedValuesCollectorImpl resolveLoadedValuesCollector( FromClause fromClause, LockingClauseStrategy lockingClauseStrategy) { final var fromClauseRoots = fromClause.getRoots(); diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/TableLock.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/TableLock.java index c7291f806b13..23d594fb8ff4 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/TableLock.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/TableLock.java @@ -53,22 +53,29 @@ public class TableLock { private final TableDetails tableDetails; private final EntityMappingType entityMappingType; - private final QuerySpec querySpec = new QuerySpec( true ); + // Used by Hibernate Reactive + protected final QuerySpec querySpec = new QuerySpec( true ); - private final NavigablePath rootPath; + // Used by Hibernate Reactive + protected final NavigablePath rootPath; private final TableReference physicalTableReference; private final TableGroup physicalTableGroup; private final TableReference logicalTableReference; - private final TableGroup logicalTableGroup; + // Used by Hibernate Reactive + protected final TableGroup logicalTableGroup; - private final LockingCreationStates creationStates; + // Used by Hibernate Reactive + protected final LockingCreationStates creationStates; - private final List resultHandlers = new ArrayList<>(); - private final List> domainResults = new ArrayList<>(); + // Used by Hibernate Reactive + protected final List resultHandlers = new ArrayList<>(); + // Used by Hibernate Reactive + protected final List> domainResults = new ArrayList<>(); - private final JdbcParameterBindings jdbcParameterBindings; + // Used by Hibernate Reactive + protected final JdbcParameterBindings jdbcParameterBindings; public TableLock( TableDetails tableDetails, @@ -262,7 +269,8 @@ public void performActions(Map entityDetailsMap, QueryOpt } - private interface ResultHandler { + // Used by Hibernate Reactive + protected interface ResultHandler { void applyResult(Object state, EntityDetails entityDetails, SharedSessionContractImplementor session); } @@ -274,7 +282,8 @@ public AbstractResultHandler(Integer statePosition) { } } - private static class NonToOneResultHandler extends AbstractResultHandler { + // Used by Hibernate Reactive + protected static class NonToOneResultHandler extends AbstractResultHandler { public NonToOneResultHandler(Integer statePosition) { super( statePosition ); } @@ -286,8 +295,9 @@ public void applyResult(Object stateValue, EntityDetails entityDetails, SharedSe } } - private static class ToOneResultHandler extends AbstractResultHandler { - private final ToOneAttributeMapping toOne; + // Used by Hibernate Reactive + protected static class ToOneResultHandler extends AbstractResultHandler { + protected final ToOneAttributeMapping toOne; public ToOneResultHandler(Integer statePosition, ToOneAttributeMapping toOne) { super( statePosition ); @@ -318,7 +328,8 @@ public void applyResult(Object stateValue, EntityDetails entityDetails, SharedSe } } - private static void applyLoadedState(EntityDetails entityDetails, Integer statePosition, Object stateValue) { + // Used by Hibernate Reactive + protected static void applyLoadedState(EntityDetails entityDetails, Integer statePosition, Object stateValue) { final var entry = entityDetails.entry(); final var loadedState = entry.getLoadedState(); if ( loadedState != null ) { @@ -331,7 +342,8 @@ private static void applyLoadedState(EntityDetails entityDetails, Integer stateP } } - private static void applyModelState(EntityDetails entityDetails, Integer statePosition, Object reference) { + // Used by Hibernate Reactive + protected static void applyModelState(EntityDetails entityDetails, Integer statePosition, Object reference) { entityDetails.key().getPersister().getAttributeMapping( statePosition ).setValue( entityDetails.instance(), reference ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelectWithActionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelectWithActionsBuilder.java new file mode 100644 index 000000000000..871b9c98d4cc --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/sql/exec/spi/JdbcSelectWithActionsBuilder.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.sql.exec.spi; + +import org.hibernate.Incubating; +import org.hibernate.LockOptions; +import org.hibernate.dialect.lock.spi.LockTimeoutType; +import org.hibernate.dialect.lock.spi.LockingSupport; +import org.hibernate.sql.ast.spi.LockingClauseStrategy; +import org.hibernate.sql.ast.tree.select.QuerySpec; + +@Incubating +// Used by Hibernate Reactive +public interface JdbcSelectWithActionsBuilder { + + JdbcSelectWithActionsBuilder setPrimaryAction(JdbcSelect primaryAction); + + JdbcSelectWithActionsBuilder setLoadedValuesCollector(LoadedValuesCollector loadedValuesCollector); + + JdbcSelectWithActionsBuilder setLockTimeoutType(LockTimeoutType lockTimeoutType); + + JdbcSelectWithActionsBuilder setLockingSupport(LockingSupport lockingSupport); + + JdbcSelectWithActionsBuilder setLockOptions(LockOptions lockOptions); + + JdbcSelectWithActionsBuilder setLockingTarget(QuerySpec lockingTarget); + + JdbcSelectWithActionsBuilder setLockingClauseStrategy(LockingClauseStrategy lockingClauseStrategy); + + JdbcSelectWithActionsBuilder setIsFollowOnLockStrategy(boolean isFollonOnLockStrategy); + + JdbcSelectWithActionsBuilder appendPreAction(PreAction... actions); + + JdbcSelectWithActionsBuilder prependPreAction(PreAction... actions); + + JdbcSelectWithActionsBuilder appendPostAction(PostAction... actions); + + JdbcSelectWithActionsBuilder prependPostAction(PostAction... actions); + + JdbcSelectWithActionsBuilder addSecondaryActionPair(SecondaryAction action); + + JdbcSelectWithActionsBuilder addSecondaryActionPair(PreAction preAction, PostAction postAction); + + JdbcSelect build(); + +}