Skip to content

Commit 7ce134f

Browse files
committed
HHH-19602 - Adjust JdbcOperation to allow more-than-one statement
1 parent 3c500c7 commit 7ce134f

File tree

8 files changed

+418
-191
lines changed

8 files changed

+418
-191
lines changed

hibernate-core/src/main/java/org/hibernate/sql/ast/spi/AbstractSqlAstTranslator.java

Lines changed: 20 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.hibernate.dialect.Dialect;
1515
import org.hibernate.dialect.DmlTargetColumnQualifierSupport;
1616
import org.hibernate.dialect.SelectItemReferenceStrategy;
17+
import org.hibernate.dialect.lock.PessimisticLockStyle;
1718
import org.hibernate.dialect.lock.spi.LockTimeoutType;
1819
import org.hibernate.dialect.lock.spi.LockingSupport;
1920
import org.hibernate.engine.jdbc.Size;
@@ -72,7 +73,6 @@
7273
import org.hibernate.sql.ast.SqlAstNodeRenderingMode;
7374
import org.hibernate.sql.ast.SqlAstTranslator;
7475
import org.hibernate.sql.ast.SqlTreeCreationException;
75-
import org.hibernate.sql.exec.internal.LockTimeoutHandler;
7676
import org.hibernate.sql.ast.internal.ParameterMarkerStrategyStandard;
7777
import org.hibernate.sql.ast.internal.TableGroupHelper;
7878
import org.hibernate.sql.ast.tree.AbstractUpdateOrDeleteStatement;
@@ -173,20 +173,24 @@
173173
import org.hibernate.sql.ast.tree.update.UpdateStatement;
174174
import org.hibernate.sql.exec.ExecutionException;
175175
import org.hibernate.sql.exec.internal.AbstractJdbcParameter;
176+
import org.hibernate.sql.exec.internal.JdbcOperationQueryDelete;
176177
import org.hibernate.sql.exec.internal.JdbcOperationQueryInsertImpl;
178+
import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect;
179+
import org.hibernate.sql.exec.internal.JdbcOperationQueryUpdate;
177180
import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl;
178181
import org.hibernate.sql.exec.internal.JdbcSelectWithActions;
182+
import org.hibernate.sql.exec.internal.LockTimeoutHandler;
179183
import org.hibernate.sql.exec.internal.SqlTypedMappingJdbcParameter;
184+
import org.hibernate.sql.exec.internal.lock.CollectionLockingAction;
185+
import org.hibernate.sql.exec.internal.lock.FollowOnLockingAction;
180186
import org.hibernate.sql.exec.spi.ExecutionContext;
181187
import org.hibernate.sql.exec.spi.JdbcLockStrategy;
182188
import org.hibernate.sql.exec.spi.JdbcOperation;
183-
import org.hibernate.sql.exec.internal.JdbcOperationQueryDelete;
184189
import org.hibernate.sql.exec.spi.JdbcOperationQueryInsert;
185-
import org.hibernate.sql.exec.internal.JdbcOperationQuerySelect;
186-
import org.hibernate.sql.exec.internal.JdbcOperationQueryUpdate;
187190
import org.hibernate.sql.exec.spi.JdbcParameterBinder;
188191
import org.hibernate.sql.exec.spi.JdbcParameterBinding;
189192
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
193+
import org.hibernate.sql.exec.spi.JdbcSelect;
190194
import org.hibernate.sql.model.MutationOperation;
191195
import org.hibernate.sql.model.ast.ColumnValueParameter;
192196
import org.hibernate.sql.model.ast.ColumnWriteFragment;
@@ -199,10 +203,6 @@
199203
import org.hibernate.sql.model.internal.TableInsertStandard;
200204
import org.hibernate.sql.model.internal.TableUpdateCustomSql;
201205
import org.hibernate.sql.model.internal.TableUpdateStandard;
202-
import org.hibernate.sql.exec.internal.lock.LockingAction;
203-
import org.hibernate.sql.exec.internal.lock.LoadedValuesCollectorImpl;
204-
import org.hibernate.sql.exec.spi.JdbcSelect;
205-
import org.hibernate.sql.exec.spi.LoadedValuesCollector;
206206
import org.hibernate.sql.results.internal.SqlSelectionImpl;
207207
import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer;
208208
import org.hibernate.type.BasicType;
@@ -907,49 +907,21 @@ protected JdbcSelect translateSelect(SelectStatement selectStatement) {
907907

908908
final LockStrategy lockStrategy = determineLockingStrategy( lockingTarget, lockOptions.getFollowOnStrategy() );
909909
if ( lockStrategy == LockStrategy.FOLLOW_ON ) {
910-
applyFollowOnLockingActions( lockOptions, lockingTarget, lockingClauseStrategy, builder );
911-
}
912-
913-
return builder.build();
914-
}
915-
916-
private void applyFollowOnLockingActions(
917-
LockOptions lockOptions,
918-
QuerySpec lockingTarget,
919-
LockingClauseStrategy lockingClauseStrategy,
920-
JdbcSelectWithActions.Builder jdbcSelectBuilder) {
921-
final FromClause fromClause = lockingTarget.getFromClause();
922-
final var loadedValuesCollector = resolveLoadedValuesCollector( fromClause, lockingClauseStrategy );
923-
924-
// NOTE: we need to set this separately so that it can get incorporated into
925-
// the JdbcValuesSourceProcessingState for proper callbacks
926-
jdbcSelectBuilder.setLoadedValuesCollector( loadedValuesCollector );
927-
928-
// additionally, add a post-action which uses the collected values.
929-
jdbcSelectBuilder.appendPostAction( new LockingAction(
930-
loadedValuesCollector,
931-
lockOptions.getLockMode(),
932-
lockOptions.getTimeout(),
933-
lockOptions.getScope()
934-
) );
935-
}
936-
937-
protected static LoadedValuesCollector resolveLoadedValuesCollector(
938-
FromClause fromClause,
939-
LockingClauseStrategy lockingClauseStrategy) {
940-
final List<TableGroup> roots = fromClause.getRoots();
941-
if ( roots.size() == 1 ) {
942-
return new LoadedValuesCollectorImpl(
943-
List.of( roots.get( 0 ).getNavigablePath() ),
944-
lockingClauseStrategy
945-
);
910+
FollowOnLockingAction.apply( lockOptions, lockingTarget, lockingClauseStrategy, builder );
946911
}
947912
else {
948-
return new LoadedValuesCollectorImpl(
949-
roots.stream().map( TableGroup::getNavigablePath ).toList(),
950-
lockingClauseStrategy
951-
);
913+
if ( lockOptions.getScope() == Locking.Scope.INCLUDE_COLLECTIONS ) {
914+
if ( dialect.getLockingSupport().getMetadata().getPessimisticLockStyle() == PessimisticLockStyle.TABLE_HINT ) {
915+
// we have a case where include-collection scope was requested.
916+
// generally this would be handled by follow-on locking process, but because
917+
// the Dialect supports "table hint locking" we will always skip follow-on
918+
// locking. therefore, we need to handle the collection-table locking explicitly.
919+
CollectionLockingAction.apply( lockOptions, lockingTarget, builder );
920+
}
921+
}
952922
}
923+
924+
return builder.build();
953925
}
954926

955927
private JdbcValuesMappingProducer buildJdbcValuesMappingProducer(SelectStatement selectStatement) {
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.sql.exec.internal.lock;
6+
7+
import jakarta.persistence.Timeout;
8+
import org.hibernate.LockMode;
9+
import org.hibernate.LockOptions;
10+
import org.hibernate.Locking;
11+
import org.hibernate.engine.spi.CollectionKey;
12+
import org.hibernate.engine.spi.EffectiveEntityGraph;
13+
import org.hibernate.engine.spi.EntityKey;
14+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
15+
import org.hibernate.graph.GraphSemantic;
16+
import org.hibernate.graph.spi.RootGraphImplementor;
17+
import org.hibernate.metamodel.mapping.EntityMappingType;
18+
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
19+
import org.hibernate.query.sqm.mutation.internal.SqmMutationStrategyHelper;
20+
import org.hibernate.spi.NavigablePath;
21+
import org.hibernate.sql.ast.tree.from.FromClause;
22+
import org.hibernate.sql.ast.tree.from.TableGroup;
23+
import org.hibernate.sql.ast.tree.select.QuerySpec;
24+
import org.hibernate.sql.exec.internal.JdbcSelectWithActions;
25+
import org.hibernate.sql.exec.spi.ExecutionContext;
26+
import org.hibernate.sql.exec.spi.LoadedValuesCollector;
27+
import org.hibernate.sql.exec.spi.PostAction;
28+
import org.hibernate.sql.exec.spi.StatementAccess;
29+
30+
import java.sql.Connection;
31+
import java.util.ArrayList;
32+
import java.util.List;
33+
import java.util.Map;
34+
35+
import static org.hibernate.sql.exec.SqlExecLogger.SQL_EXEC_LOGGER;
36+
import static org.hibernate.sql.exec.internal.lock.LockingHelper.segmentLoadedValues;
37+
38+
/**
39+
* PostAction intended to perform collection locking with
40+
* {@linkplain Locking.Scope#INCLUDE_COLLECTIONS} for Dialects
41+
* which support "table hint locking" (T-SQL variants).
42+
*
43+
* @author Steve Ebersole
44+
*/
45+
public class CollectionLockingAction implements PostAction {
46+
private final LoadedValuesCollectorImpl loadedValuesCollector;
47+
private final LockMode lockMode;
48+
private final Timeout lockTimeout;
49+
50+
private CollectionLockingAction(
51+
LoadedValuesCollectorImpl loadedValuesCollector,
52+
LockMode lockMode,
53+
Timeout lockTimeout) {
54+
this.loadedValuesCollector = loadedValuesCollector;
55+
this.lockMode = lockMode;
56+
this.lockTimeout = lockTimeout;
57+
}
58+
59+
public static void apply(
60+
LockOptions lockOptions,
61+
QuerySpec lockingTarget,
62+
JdbcSelectWithActions.Builder jdbcSelectBuilder) {
63+
assert lockOptions.getScope() == Locking.Scope.INCLUDE_COLLECTIONS;
64+
65+
final var loadedValuesCollector = resolveLoadedValuesCollector( lockingTarget.getFromClause() );
66+
67+
// NOTE: we need to set this separately so that it can get incorporated into
68+
// the JdbcValuesSourceProcessingState for proper callbacks
69+
jdbcSelectBuilder.setLoadedValuesCollector( loadedValuesCollector );
70+
71+
// additionally, add a post-action which uses the collected values.
72+
jdbcSelectBuilder.appendPostAction( new CollectionLockingAction(
73+
loadedValuesCollector,
74+
lockOptions.getLockMode(),
75+
lockOptions.getTimeout()
76+
) );
77+
}
78+
79+
@Override
80+
public void performPostAction(
81+
StatementAccess jdbcStatementAccess,
82+
Connection jdbcConnection,
83+
ExecutionContext executionContext) {
84+
LockingHelper.logLoadedValues( loadedValuesCollector );
85+
86+
final SharedSessionContractImplementor session = executionContext.getSession();
87+
88+
// NOTE: we deal with effective graphs here to make sure embedded associations are treated as lazy
89+
final EffectiveEntityGraph effectiveEntityGraph = session.getLoadQueryInfluencers().getEffectiveEntityGraph();
90+
final RootGraphImplementor<?> initialGraph = effectiveEntityGraph.getGraph();
91+
final GraphSemantic initialSemantic = effectiveEntityGraph.getSemantic();
92+
93+
// collect registrations by entity type
94+
final Map<EntityMappingType, List<EntityKey>> entitySegments = segmentLoadedValues( loadedValuesCollector );
95+
96+
try {
97+
// for each entity-type, prepare a locking select statement per table.
98+
// this is based on the attributes for "state array" ordering purposes -
99+
// we match each attribute to the table it is mapped to and add it to
100+
// the select-list for that table-segment.
101+
entitySegments.forEach( (entityMappingType, entityKeys) -> {
102+
if ( SQL_EXEC_LOGGER.isDebugEnabled() ) {
103+
SQL_EXEC_LOGGER.debugf( "Starting include-collections locking process - %s",
104+
entityMappingType.getEntityName() );
105+
}
106+
107+
// apply an empty "fetch graph" to make sure any embedded associations reachable from
108+
// any of the DomainResults we will create are treated as lazy
109+
final RootGraphImplementor<?> graph = entityMappingType.createRootGraph( session );
110+
effectiveEntityGraph.clear();
111+
effectiveEntityGraph.applyGraph( graph, GraphSemantic.FETCH );
112+
113+
// create a cross-reference of information related to an entity based on its identifier.
114+
// we use this as the collection owners whose collections need to be locked
115+
final Map<Object, EntityDetails> entityDetailsMap = LockingHelper.resolveEntityKeys( entityKeys, executionContext );
116+
117+
SqmMutationStrategyHelper.visitCollectionTables( entityMappingType, (attribute) -> {
118+
// we may need to lock the "collection table".
119+
// the conditions are a bit unclear as to directionality, etc., so for now lock each.
120+
LockingHelper.lockCollectionTable(
121+
attribute,
122+
lockMode,
123+
lockTimeout,
124+
entityDetailsMap,
125+
executionContext
126+
);
127+
} );
128+
} );
129+
}
130+
finally {
131+
// reset the effective graph to whatever it was when we started
132+
effectiveEntityGraph.clear();
133+
session.getLoadQueryInfluencers().applyEntityGraph( initialGraph, initialSemantic );
134+
}
135+
}
136+
137+
private static LoadedValuesCollectorImpl resolveLoadedValuesCollector(FromClause fromClause) {
138+
final List<TableGroup> roots = fromClause.getRoots();
139+
if ( roots.size() == 1 ) {
140+
return new LoadedValuesCollectorImpl(
141+
List.of( roots.get( 0 ).getNavigablePath() )
142+
);
143+
}
144+
else {
145+
return new LoadedValuesCollectorImpl(
146+
roots.stream().map( TableGroup::getNavigablePath ).toList()
147+
);
148+
}
149+
}
150+
151+
private static class LoadedValuesCollectorImpl implements LoadedValuesCollector {
152+
private final List<NavigablePath> rootPaths;
153+
154+
private List<LoadedEntityRegistration> rootEntitiesToLock;
155+
private List<LoadedEntityRegistration> nonRootEntitiesToLock;
156+
private List<LoadedCollectionRegistration> collectionsToLock;
157+
158+
private LoadedValuesCollectorImpl(List<NavigablePath> rootPaths) {
159+
this.rootPaths = rootPaths;
160+
}
161+
162+
@Override
163+
public void registerEntity(NavigablePath navigablePath, EntityMappingType entityDescriptor, EntityKey entityKey) {
164+
if ( rootPaths.contains( navigablePath ) ) {
165+
if ( rootEntitiesToLock == null ) {
166+
rootEntitiesToLock = new ArrayList<>();
167+
}
168+
rootEntitiesToLock.add( new LoadedEntityRegistration( navigablePath, entityDescriptor, entityKey ) );
169+
}
170+
else {
171+
if ( nonRootEntitiesToLock == null ) {
172+
nonRootEntitiesToLock = new ArrayList<>();
173+
}
174+
nonRootEntitiesToLock.add( new LoadedEntityRegistration( navigablePath, entityDescriptor, entityKey ) );
175+
}
176+
}
177+
178+
@Override
179+
public void registerCollection(NavigablePath navigablePath, PluralAttributeMapping collectionDescriptor, CollectionKey collectionKey) {
180+
if ( collectionsToLock == null ) {
181+
collectionsToLock = new ArrayList<>();
182+
}
183+
collectionsToLock.add( new LoadedCollectionRegistration( navigablePath, collectionDescriptor, collectionKey ) );
184+
}
185+
186+
@Override
187+
public void clear() {
188+
if ( rootEntitiesToLock != null ) {
189+
rootEntitiesToLock.clear();
190+
}
191+
if ( nonRootEntitiesToLock != null ) {
192+
nonRootEntitiesToLock.clear();
193+
}
194+
if ( collectionsToLock != null ) {
195+
collectionsToLock.clear();
196+
}
197+
}
198+
199+
@Override
200+
public List<LoadedEntityRegistration> getCollectedRootEntities() {
201+
return rootEntitiesToLock;
202+
}
203+
204+
@Override
205+
public List<LoadedEntityRegistration> getCollectedNonRootEntities() {
206+
return nonRootEntitiesToLock;
207+
}
208+
209+
@Override
210+
public List<LoadedCollectionRegistration> getCollectedCollections() {
211+
return collectionsToLock;
212+
}
213+
}
214+
}

0 commit comments

Comments
 (0)