{"record":{"id":"e9ef413c2e1f949c","repo":"hibernate/hibernate-orm","slug":"locking-with-set-operators-is-not-supported","errorCode":null,"errorMessage":"Locking with set operators is not supported","messagePattern":"Locking with set operators is not supported","errorType":"exception","errorClass":"IllegalQueryOperationException","httpStatus":null,"severity":"error","filePath":"hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacySqlAstTranslator.java","lineNumber":158,"sourceCode":"\t\t}\n\t\tsuper.visitSqlSelection( sqlSelection );\n\t}\n\n\t@Override\n\tprotected LockStrategy determineLockingStrategy(\n\t\t\tQuerySpec querySpec,\n\t\t\tLocking.FollowOn followOnStrategy) {\n\t\tif ( followOnStrategy == Locking.FollowOn.FORCE ) {\n\t\t\treturn LockStrategy.FOLLOW_ON;\n\t\t}\n\n\t\tLockStrategy strategy = super.determineLockingStrategy( querySpec, followOnStrategy );\n\n\t\t// Oracle also doesn't support locks with set operators\n\t\t// See https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_10002.htm#i2066346\n\t\tif ( strategy != LockStrategy.FOLLOW_ON && isPartOfQueryGroup() ) {\n\t\t\tif ( followOnStrategy == Locking.FollowOn.DISALLOW ) {\n\t\t\t\tthrow new IllegalQueryOperationException( \"Locking with set operators is not supported\" );\n\t\t\t}\n\t\t\telse if ( followOnStrategy != Locking.FollowOn.IGNORE ) {\n\t\t\t\tstrategy = LockStrategy.NONE;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tstrategy = LockStrategy.FOLLOW_ON;\n\t\t\t}\n\t\t}\n\n\t\tif ( strategy != LockStrategy.FOLLOW_ON && hasSetOperations( querySpec ) ) {\n\t\t\tif ( followOnStrategy == Locking.FollowOn.DISALLOW ) {\n\t\t\t\tthrow new IllegalQueryOperationException( \"Locking with set operators is not supported\" );\n\t\t\t}\n\t\t\telse if ( followOnStrategy != Locking.FollowOn.IGNORE ) {\n\t\t\t\tstrategy = LockStrategy.NONE;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tstrategy = LockStrategy.FOLLOW_ON;","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/OracleLegacySqlAstTranslator.java#L140-L176","documentation":"Oracle cannot apply FOR UPDATE to a SELECT participating in a set operation (union/intersect/minus; see the Oracle docs link in the source). OracleLegacySqlAstTranslator.determineLockingStrategy() normally degrades to follow-on locking (rows locked by a follow-up query); when follow-on locking is disallowed (FollowOn.DISALLOW, e.g. hibernate.query.followOnLocking=false) it throws IllegalQueryOperationException instead as soon as the query group contains set operators.","triggerScenarios":"HQL/Criteria using union/intersect/except combined with a pessimistic lock (setLockMode(PESSIMISTIC_WRITE) or LockOptions) while follow-on locking is disabled, on OracleLegacyDialect.","commonSituations":"Follow-on locking switched off globally for performance; a pessimistic lock added to an existing union-based search or report query; pagination plus locking on merged result sets.","solutions":["Re-enable follow-on locking (remove the hibernate.query.followOnLocking=false setting) so Hibernate applies the lock in a follow-up select","Restructure the query to remove set operators around the locked query spec (compose results in the application)","Run the union query unlocked and lock rows separately with a native 'select ... for update' on the key set"],"exampleFix":"// before: follow-on locking disallowed + union\nList<Order> l = session.createQuery( unionHql, Order.class )\n        .setLockMode( \"o\", LockMode.PESSIMISTIC_WRITE )\n        .list(); // -> IllegalQueryOperationException\n\n// after: allow follow-on locking (drop hibernate.query.followOnLocking=false)\nList<Order> l = session.createQuery( unionHql, Order.class )\n        .setLockMode( \"o\", LockMode.PESSIMISTIC_WRITE ) // follow-up select applies the lock\n        .list();","handlingStrategy":"try-catch","validationCode":"boolean hasSetOps(String hql) {\n    return hql != null && hql.toLowerCase( Locale.ROOT ).matches( \"(?s).*\\\\b(union|intersect|except)\\\\b.*\" );\n}\n\nif ( hasSetOps( hql ) && lockMode.greaterThan( LockMode.OPTIMISTIC ) ) {\n    // run unlocked or allow follow-on locking instead of failing translation\n}","typeGuard":null,"tryCatchPattern":"try {\n    return query.list();\n}\ncatch ( IllegalQueryOperationException e ) {\n    if ( e.getMessage() != null && e.getMessage().contains( \"set operators\" ) ) {\n        query.setLockOptions( LockOptions.NONE ); // re-run unlocked, lock separately by ids\n        return query.list();\n    }\n    throw e;\n}","preventionTips":["Do not disable follow-on locking globally on Oracle if any query uses set operators","Lock a simple id query and hydrate from it instead of locking a union","Add integration tests with production-shaped HQL plus pessimistic locks"],"tags":["oracle","pessimistic-locking","union","query-translation"],"backgroundTag":"pessimistic-locking-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}