hibernate/hibernate-orm · error · IllegalQueryOperationException
Locking with joins is not supported
Error message
Locking with joins is not supported
What it means
For aggregate-typed columns (SQL STRUCT/aggregate and JSON mappings), AggregateColumnWriteExpression keeps parallel arrays - selectableMappings[] and valueExpressions[] - and getValueExpression(selectableMapping) resolves by instance identity (==, not equals). IllegalArgumentException means the SelectableMapping you asked with is not one of the mappings this aggregate write expression was built from, i.e. the caller used a mapping from a different part of the mapping model.
Source
Thrown at hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/AltibaseSqlAstTranslator.java:242
else {
super.visitQuerySpec( querySpec );
}
}
protected boolean shouldEmulateFetchClause(QueryPart queryPart) {
// Check if current query part is already row numbering to avoid infinite recursion
return useOffsetFetchClause( queryPart ) && getQueryPartForRowNumbering() != queryPart
&& getDialect().supportsWindowFunctions() && !isRowsOnlyFetchClauseType( queryPart );
}
@Override
protected LockStrategy determineLockingStrategy(QuerySpec querySpec, Locking.FollowOn followOnStrategy) {
final LockStrategy lockStrategy = super.determineLockingStrategy( querySpec, followOnStrategy );
final LockingClauseStrategy lockingClauseStrategy = getLockingClauseStrategy();
if ( lockingClauseStrategy != null && lockingClauseStrategy.containsJoins() ) {
// Altibase does not allow FOR UPDATE when the query also contains joins.
if ( followOnStrategy == Locking.FollowOn.DISALLOW ) {
throw new IllegalQueryOperationException( "Locking with joins is not supported" );
}
else if ( followOnStrategy == Locking.FollowOn.IGNORE ) {
return LockStrategy.NONE;
}
return LockStrategy.FOLLOW_ON;
}
return lockStrategy;
}
@Override
protected void renderPartitionItem(Expression expression) {
if ( expression instanceof Literal ) {
appendSql( "'0' || '0'" );
}
else if ( expression instanceof Summarization ) {
// This could theoretically be emulated by rendering all grouping variations of the query and
// connect them via union all but that's probably pretty inefficient and would have to happen
// on the query spec levelView on GitHub (pinned to fad1729dce)
Solutions
- Always obtain the SelectableMapping from the same aggregate mapping that produced the write expression: iterate mapping.forEachSelectable(...) and pass exactly those instances.
- Never cache SelectableMapping instances across SessionFactory lifecycles - re-fetch them from the current mapping.
- Align custom aggregate-column rendering code with the current Hibernate version's AggregateColumnWriteExpression API.
- Upgrade Hibernate; if the call comes from core rendering, report HHH with the aggregate mapping.
Example fix
// before: asking with a mapping from another source
Expression e = writeExpr.getValueExpression( foreignMapping.getSelectable() );
// after: resolve through the aggregate's own selectables
final Expression[] found = new Expression[1];
aggregateMapping.forEachSelectable( (i, sel) -> {
if ( sel == candidateSelectable ) found[0] = writeExpr.getValueExpression( sel );
} ); Defensive patterns
Strategy: type-guard
Validate before calling
// Verify the candidate mapping belongs to this aggregate write expression before asking
boolean owned = false;
aggregateMapping.forEachSelectable( (i, sel) -> { if ( sel == candidateSelectable ) owned = true; } );
if ( !owned ) {
throw new IllegalArgumentException("candidateSelectable is not part of this aggregate mapping");
} Type guard
// Java: only resolve value expressions through the aggregate's own selectables
static Expression valueExpressionFor(AggregateColumnWriteExpression write,
AggregateMapping owner,
SelectableMapping candidate) {
final boolean[] found = { false };
owner.forEachSelectable( (i, sel) -> { if ( sel == candidate ) found[0] = true; } );
if ( !found[0] ) {
return null; // caller falls back / reports instead of risking IllegalArgumentException
}
return write.getValueExpression( candidate );
} Try / catch
try {
return writeExpr.getValueExpression( selectable );
} catch ( IllegalArgumentException e ) {
if ( e.getMessage() != null && e.getMessage().startsWith("Couldn't find value expression") ) {
// re-resolve the selectable from the current aggregate mapping and retry
return writeExpr.getValueExpression( resolveFreshSelectable( aggregateMapping, selectable ) );
}
throw e;
} Prevention
- Never pass SelectableMapping instances that were not handed to you by the same aggregate mapping's forEachSelectable.
- Do not cache SelectableMapping instances across SessionFactory rebuilds.
- Re-verify custom JdbcType rendering code against the current Hibernate version's aggregate write API after upgrades.
When it happens
Trigger: Custom JdbcType/SqlAstTranslator or aggregate-mapping code calling getValueExpression with a SelectableMapping obtained elsewhere (another aggregate part, a rebuilt mapping instance) instead of one delivered by this aggregate mapping's forEachSelectable; also possible after metadata enhancement creates duplicate mapping instances for the same column.
Common situations: Projects with custom aggregate/JSON JdbcTypes after a Hibernate upgrade changed how selectable mapping instances are created and cached; code that caches SelectableMappings across SessionFactory rebuilds; mixing mapping instances from two metamodels.
Related errors
- Could not find selectable [%s] in embeddable type [%s] for J
- Can't parse JSON object for selectable [%s] which is not of
- Expected JSON object end, but none found.
- Malformed JSON. Expected object but got: " + event
- Failed to serialize JSON mapping
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/4f988dc9c843aab8.
Report an issue: GitHub.