55package org .hibernate .sql .exec .internal .lock ;
66
77import jakarta .persistence .Timeout ;
8- import org .hibernate .AssertionFailure ;
98import org .hibernate .LockMode ;
109import org .hibernate .ScrollMode ;
10+ import org .hibernate .Session ;
1111import org .hibernate .collection .spi .PersistentCollection ;
1212import org .hibernate .engine .jdbc .spi .JdbcServices ;
1313import org .hibernate .engine .spi .EntityEntry ;
2222import org .hibernate .metamodel .mapping .ValuedModelPart ;
2323import org .hibernate .query .internal .QueryOptionsImpl ;
2424import org .hibernate .query .spi .QueryOptions ;
25+ import org .hibernate .query .sqm .ComparisonOperator ;
2526import org .hibernate .spi .NavigablePath ;
2627import org .hibernate .sql .ast .SqlAstTranslator ;
2728import org .hibernate .sql .ast .SqlAstTranslatorFactory ;
2829import org .hibernate .sql .ast .spi .LockingClauseStrategy ;
2930import org .hibernate .sql .ast .tree .expression .ColumnReference ;
31+ import org .hibernate .sql .ast .tree .expression .JdbcParameter ;
32+ import org .hibernate .sql .ast .tree .expression .SqlTuple ;
3033import org .hibernate .sql .ast .tree .from .NamedTableReference ;
3134import org .hibernate .sql .ast .tree .from .TableGroup ;
3235import org .hibernate .sql .ast .tree .from .TableGroupJoin ;
36+ import org .hibernate .sql .ast .tree .from .TableReference ;
37+ import org .hibernate .sql .ast .tree .predicate .ComparisonPredicate ;
3338import org .hibernate .sql .ast .tree .predicate .InListPredicate ;
3439import org .hibernate .sql .ast .tree .select .QuerySpec ;
3540import org .hibernate .sql .ast .tree .select .SelectStatement ;
4853import java .util .ArrayList ;
4954import java .util .Collection ;
5055import java .util .HashMap ;
51- import java .util .IdentityHashMap ;
5256import java .util .LinkedHashSet ;
5357import java .util .List ;
5458import java .util .Map ;
6165 * @author Steve Ebersole
6266 */
6367public class LockingHelper {
68+ /**
69+ * Lock a collection-table for a single entity.
70+ *
71+ * @param attributeMapping The plural attribute whose table needs locked.
72+ * @param lockMode The lock mode to apply
73+ * @param lockTimeout A lock timeout to apply, if one.
74+ * @param collectionToLock The collection to lock.
75+ *
76+ * @see Session#lock
77+ */
78+ public static void lockCollectionTable (
79+ PluralAttributeMapping attributeMapping ,
80+ LockMode lockMode ,
81+ Timeout lockTimeout ,
82+ PersistentCollection <?> collectionToLock ,
83+ ExecutionContext executionContext ) {
84+ final SharedSessionContractImplementor session = executionContext .getSession ();
85+
86+ final ForeignKeyDescriptor keyDescriptor = attributeMapping .getKeyDescriptor ();
87+ final String keyTableName = keyDescriptor .getKeyTable ();
88+
89+ if ( SQL_EXEC_LOGGER .isDebugEnabled () ) {
90+ SQL_EXEC_LOGGER .debugf ( "Collection locking for collection table `%s` - %s" , keyTableName , attributeMapping .getRootPathName () );
91+ }
92+
93+ final QuerySpec querySpec = new QuerySpec ( true );
94+
95+ final NamedTableReference tableReference = new NamedTableReference ( keyTableName , "tbl" );
96+ final LockingTableGroup tableGroup = new LockingTableGroup (
97+ tableReference ,
98+ keyTableName ,
99+ attributeMapping ,
100+ keyDescriptor .getKeySide ().getModelPart ()
101+ );
102+
103+ querySpec .getFromClause ().addRoot ( tableGroup );
104+
105+ final ValuedModelPart keyPart = keyDescriptor .getKeyPart ();
106+ final ColumnReference columnReference = new ColumnReference ( tableReference , keyPart .getSelectable ( 0 ) );
107+
108+ // NOTE: We add the key column to the selection list, but never create a DomainResult
109+ // as we won't read the value back. Ideally, we would read the "value column(s)" and
110+ // update the collection state accordingly much like is done for entity state -
111+ // however, the concern is minor, so for simplicity we do not.
112+ final SqlSelectionImpl sqlSelection = new SqlSelectionImpl ( columnReference , 0 );
113+ querySpec .getSelectClause ().addSqlSelection ( sqlSelection );
114+
115+ final JdbcParameterBindingsImpl parameterBindings = new JdbcParameterBindingsImpl ( keyDescriptor .getJdbcTypeCount () );
116+
117+ final ComparisonPredicate restriction ;
118+ if ( keyDescriptor .getJdbcTypeCount () == 1 ) {
119+ final JdbcParameterImpl jdbcParameter = new JdbcParameterImpl ( keyPart .getSelectable ( 0 ).getJdbcMapping () );
120+ keyDescriptor .breakDownJdbcValues (
121+ collectionToLock .getKey (),
122+ (valueIndex , value , jdbcValueMapping ) -> {
123+ parameterBindings .addBinding (
124+ jdbcParameter ,
125+ new JdbcParameterBindingImpl ( jdbcValueMapping .getJdbcMapping (), value )
126+ );
127+ },
128+ session
129+ );
130+ restriction = new ComparisonPredicate ( columnReference , ComparisonOperator .EQUAL , jdbcParameter );
131+ }
132+ else {
133+ final List <ColumnReference > columnReferences = new ArrayList <>( keyDescriptor .getJdbcTypeCount () );
134+ final List <JdbcParameter > jdbcParameters = new ArrayList <>( keyDescriptor .getJdbcTypeCount () );
135+ keyDescriptor .breakDownJdbcValues (
136+ collectionToLock .getKey (),
137+ (valueIndex , value , jdbcValueMapping ) -> {
138+ columnReferences .add ( new ColumnReference ( tableReference , jdbcValueMapping ) );
139+
140+ final JdbcParameterImpl jdbcParameter = new JdbcParameterImpl ( jdbcValueMapping .getJdbcMapping () );
141+ jdbcParameters .add ( jdbcParameter );
142+ parameterBindings .addBinding (
143+ jdbcParameter ,
144+ new JdbcParameterBindingImpl ( jdbcValueMapping .getJdbcMapping (), value )
145+ );
146+ },
147+ session
148+ );
149+ final SqlTuple columns = new SqlTuple ( columnReferences , keyDescriptor );
150+ final SqlTuple parameters = new SqlTuple ( jdbcParameters , keyDescriptor );
151+ restriction = new ComparisonPredicate ( columns , ComparisonOperator .EQUAL , parameters );
152+ }
153+ querySpec .applyPredicate ( restriction );
154+
155+ final QueryOptionsImpl lockingQueryOptions = new QueryOptionsImpl ();
156+ lockingQueryOptions .getLockOptions ().setLockMode ( lockMode );
157+ lockingQueryOptions .getLockOptions ().setTimeout ( lockTimeout );
158+ final ExecutionContext lockingExecutionContext = new BaseExecutionContext ( executionContext .getSession () ) {
159+ @ Override
160+ public QueryOptions getQueryOptions () {
161+ return lockingQueryOptions ;
162+ }
163+ };
164+
165+ performLocking ( querySpec , parameterBindings , lockingExecutionContext );
166+ }
167+
64168 /**
65169 * Lock a collection-table.
66170 *
@@ -104,13 +208,12 @@ public static void lockCollectionTable(
104208 final SqlSelectionImpl sqlSelection = new SqlSelectionImpl ( columnReference , 0 );
105209 querySpec .getSelectClause ().addSqlSelection ( sqlSelection );
106210
107- final InListPredicate restriction = new InListPredicate ( columnReference );
108- querySpec .applyPredicate ( restriction );
109-
110211 final int expectedParamCount = ownerDetailsMap .size () * keyDescriptor .getJdbcTypeCount ();
111212 final JdbcParameterBindingsImpl parameterBindings = new JdbcParameterBindingsImpl ( expectedParamCount );
112213
214+ final InListPredicate restriction ;
113215 if ( keyDescriptor .getJdbcTypeCount () == 1 ) {
216+ restriction = new InListPredicate ( columnReference );
114217 applySimpleCollectionKeyTableLockRestrictions (
115218 attributeMapping ,
116219 keyDescriptor ,
@@ -121,15 +224,16 @@ public static void lockCollectionTable(
121224 );
122225 }
123226 else {
124- applyCompositeCollectionKeyTableLockRestrictions (
227+ restriction = applyCompositeCollectionKeyTableLockRestrictions (
125228 attributeMapping ,
126229 keyDescriptor ,
127- restriction ,
230+ tableReference ,
128231 parameterBindings ,
129232 ownerDetailsMap ,
130233 executionContext .getSession ()
131234 );
132235 }
236+ querySpec .applyPredicate ( restriction );
133237
134238 final QueryOptionsImpl lockingQueryOptions = new QueryOptionsImpl ();
135239 lockingQueryOptions .getLockOptions ().setLockMode ( lockMode );
@@ -172,14 +276,48 @@ private static void applySimpleCollectionKeyTableLockRestrictions(
172276 } );
173277 }
174278
175- private static void applyCompositeCollectionKeyTableLockRestrictions (
279+ private static InListPredicate applyCompositeCollectionKeyTableLockRestrictions (
176280 PluralAttributeMapping attributeMapping ,
177281 ForeignKeyDescriptor keyDescriptor ,
178- InListPredicate restriction ,
282+ TableReference tableReference ,
179283 JdbcParameterBindingsImpl parameterBindings ,
180284 Map <Object , EntityDetails > ownerDetailsMap ,
181285 SharedSessionContractImplementor session ) {
182- throw new UnsupportedOperationException ( "Not implemented yet" );
286+ if ( !session .getDialect ().supportsRowValueConstructorSyntaxInInList () ) {
287+ // for now...
288+ throw new UnsupportedOperationException (
289+ "Follow-on collection-table locking with composite keys is not supported for Dialects"
290+ + " which do not support tuples (row constructor syntax) as part of an in-list"
291+ );
292+ }
293+
294+ final List <ColumnReference > columnReferences = new ArrayList <>( keyDescriptor .getJdbcTypeCount () );
295+ keyDescriptor .forEachSelectable ( (selectionIndex , selectableMapping ) -> {
296+ columnReferences .add ( new ColumnReference ( tableReference , selectableMapping ) );
297+ } );
298+ final InListPredicate inListPredicate = new InListPredicate ( new SqlTuple ( columnReferences , keyDescriptor ) );
299+
300+ ownerDetailsMap .forEach ( (o , entityDetails ) -> {
301+ final PersistentCollection <?> collectionInstance = (PersistentCollection <?>) entityDetails .entry ().getLoadedState ()[attributeMapping .getStateArrayPosition ()];
302+ final Object collectionKeyValue = collectionInstance .getKey ();
303+
304+ final List <JdbcParameter > jdbcParameters = new ArrayList <>( keyDescriptor .getJdbcTypeCount () );
305+ keyDescriptor .breakDownJdbcValues (
306+ collectionKeyValue ,
307+ (valueIndex , value , jdbcValueMapping ) -> {
308+ final JdbcParameterImpl jdbcParameter = new JdbcParameterImpl ( jdbcValueMapping .getJdbcMapping () );
309+ jdbcParameters .add ( jdbcParameter );
310+ parameterBindings .addBinding (
311+ jdbcParameter ,
312+ new JdbcParameterBindingImpl ( jdbcValueMapping .getJdbcMapping (), value )
313+ );
314+ },
315+ session
316+ );
317+ inListPredicate .addExpression ( new SqlTuple ( jdbcParameters , keyDescriptor ) );
318+ } );
319+
320+ return inListPredicate ;
183321 }
184322
185323 private static void performLocking (
@@ -262,20 +400,7 @@ public static Collection<NavigablePath> extractPathsToLock(LockingClauseStrategy
262400 return paths ;
263401 }
264402
265- /**
266- * Collect loaded entities by type.
267- */
268- public static Map <EntityMappingType , List <EntityKey >> segmentLoadedValues (LoadedValuesCollector loadedValuesCollector ) {
269- final Map <EntityMappingType , List <EntityKey >> map = new IdentityHashMap <>();
270- segmentLoadedValues ( loadedValuesCollector .getCollectedRootEntities (), map );
271- segmentLoadedValues ( loadedValuesCollector .getCollectedNonRootEntities (), map );
272- if ( map .isEmpty () ) {
273- throw new AssertionFailure ( "Expecting some values" );
274- }
275- return map ;
276- }
277-
278- private static void segmentLoadedValues (List <LoadedValuesCollector .LoadedEntityRegistration > registrations , Map <EntityMappingType , List <EntityKey >> map ) {
403+ public static void segmentLoadedValues (List <LoadedValuesCollector .LoadedEntityRegistration > registrations , Map <EntityMappingType , List <EntityKey >> map ) {
279404 if ( registrations == null ) {
280405 return ;
281406 }
0 commit comments