{"record":{"id":"f200b12ac1c263f0","repo":"hibernate/hibernate-orm","slug":"follow-on-collection-table-locking-with-composite","errorCode":null,"errorMessage":"Follow-on collection-table locking with composite keys is not supported for Dialects which do not support tuples (row constructor syntax) as part of an in-list","messagePattern":"Follow-on collection-table locking with composite keys is not supported for Dialects which do not support tuples \\(row constructor syntax\\) as part of an in-list","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/LockingHelper.java","lineNumber":268,"sourceCode":"\t\t\t\t\t\trestriction.addExpression( jdbcParameter );\n\t\t\t\t\t\tparameterBindings.addBinding( jdbcParameter,\n\t\t\t\t\t\t\t\tnew JdbcParameterBindingImpl( jdbcMapping, value ) );\n\t\t\t\t\t},\n\t\t\t\t\tsession\n\t\t\t);\n\t\t} );\n\t}\n\n\tprivate static InListPredicate applyCompositeCollectionKeyTableLockRestrictions(\n\t\t\tPluralAttributeMapping attributeMapping,\n\t\t\tForeignKeyDescriptor keyDescriptor,\n\t\t\tTableReference tableReference,\n\t\t\tJdbcParameterBindingsImpl parameterBindings,\n\t\t\tMap<Object, EntityDetails> ownerDetailsMap,\n\t\t\tSharedSessionContractImplementor session) {\n\t\tif ( !session.getDialect().supportsRowValueConstructorSyntaxInInList() ) {\n\t\t\t// for now...\n\t\t\tthrow new UnsupportedOperationException(\n\t\t\t\t\t\"Follow-on collection-table locking with composite keys is not supported for Dialects\"\n\t\t\t\t\t+ \" which do not support tuples (row constructor syntax) as part of an in-list\"\n\t\t\t);\n\t\t}\n\n\t\tfinal int jdbcTypeCount = keyDescriptor.getJdbcTypeCount();\n\t\tfinal List<ColumnReference> columnReferences = new ArrayList<>( jdbcTypeCount );\n\t\tkeyDescriptor.forEachSelectable( (selectionIndex, selectableMapping) -> {\n\t\t\tcolumnReferences.add( new ColumnReference( tableReference, selectableMapping ) );\n\t\t} );\n\t\tfinal InListPredicate inListPredicate = new InListPredicate( new SqlTuple( columnReferences, keyDescriptor ) );\n\n\t\townerDetailsMap.forEach( (o, entityDetails) -> {\n\t\t\tfinal var collectionInstance =\n\t\t\t\t\t(PersistentCollection<?>)\n\t\t\t\t\t\t\tentityDetails.entry().getLoadedState()[attributeMapping.getStateArrayPosition()];\n\t\t\tfinal Object collectionKeyValue = collectionInstance.getKey();\n","sourceCodeStart":250,"sourceCodeEnd":286,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/sql/exec/internal/lock/LockingHelper.java#L250-L286","documentation":"When a collection with a composite key must be locked in the collection table via follow-on locking, LockingHelper builds an IN-list of tuple row constructors ((k1,k2) IN ((?,?),(?,?))). That syntax is only emitted when Dialect.supportsRowValueConstructorSyntaxInInList() returns true; otherwise applyCompositeCollectionKeyTableLockRestrictions (the ownerDetailsMap overload) throws UnsupportedOperationException - a known platform limitation, marked 'for now' in the source.","triggerScenarios":"Pessimistic locking of a collection whose owner FK is composite (owner entity uses @EmbeddedId/@IdClass) - e.g. session.buildLockRequest(...).lock() on an initialized collection or a query with LockMode.PESSIMISTIC_WRITE fetching it - on a dialect where supportsRowValueConstructorSyntaxInInList() is false (SQL Server, MySQL).","commonSituations":"Entities with composite identifiers migrated to databases whose dialect lacks tuple IN-list support; softwares relying on collection-table locking with composite FKs; tests passing on PostgreSQL/DB2 but failing on SQL Server/MySQL.","solutions":["Lock the owner row instead of the collection, avoiding follow-on collection-table locking","Rewrite the lock as explicit SQL with AND-ed per-column predicates via a native query","Use a dialect whose database supports row-value constructors in IN-lists (Oracle, DB2, PostgreSQL), or override supportsRowValueConstructorSyntaxInInList() if your database actually supports it","Remap the collection with a single-column key (collection id / surrogate join-table key) instead of a composite FK"],"exampleFix":"-- before (Hibernate-issued tuple IN, unsupported on this dialect)\n-- where (k1, k2) in ((?, ?), (?, ?)) for update\n\n-- after (native locking query, dialect-safe)\nselect id from CollectionTable where ownerId = :id and ownerKey2 = :k2 for update","handlingStrategy":"fallback","validationCode":"boolean tupleInListSupported(SharedSessionContractImplementor session) {\n    return session.getDialect().supportsRowValueConstructorSyntaxInInList();\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Lock owner rows rather than collection tables when collections have composite keys on tuple-IN-less dialects","Check dialect.supportsRowValueConstructorSyntaxInInList() before requesting collection-table locks","Prefer single-column collection keys (surrogate/collection id) on SQL Server/MySQL"],"tags":["hibernate","orm","locking","composite-key","dialect-limitation","in-list"],"backgroundTag":"row-value-constructor-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}