{"record":{"id":"a0a339f9627e2ac6","repo":"hibernate/hibernate-orm","slug":"locking-with-set-operators-is-not-supported-a0a339","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/TimesTenSqlAstTranslator.java","lineNumber":52,"sourceCode":"\n\tpublic TimesTenSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {\n\t\tsuper( sessionFactory, statement );\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\t// TimesTen supports locks with aggregates but not with set operators\n\t\t// See https://docs.oracle.com/cd/E11882_01/timesten.112/e21642/state.htm#TTSQL329\n\t\tLockStrategy strategy = LockStrategy.CLAUSE;\n\t\tif ( getQueryPartStack().findCurrentFirst( part -> part instanceof QueryGroup ? part : null ) != null ) {\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\t\treturn strategy;\n\t}\n\n\t@Override\n\tprotected void visitSqlSelections(SelectClause selectClause) {\n\t\trenderRowsToClause( (QuerySpec) getQueryPartStack().getCurrent() );\n\t\tsuper.visitSqlSelections( selectClause );\n\t}\n\n\t@Override","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenSqlAstTranslator.java#L34-L70","documentation":"TimesTen supports row locks with aggregates but not with set operators (union/intersect/except). When a locking clause is required while a QueryGroup sits on the query-part stack and follow-on locking is DISALLOWed, determineLockingStrategy throws IllegalQueryOperationException: TimesTen cannot attach a lock to a set operation and the follow-on fallback is forbidden.","triggerScenarios":"A query containing UNION/INTERSECT/EXCEPT combined with a pessimistic lock (LockModeType.PESSIMISTIC_WRITE, setLockMode, HQL 'for update') while follow-on locking is disallowed — e.g. query.setFollowOnStrategy(Locking.FollowOn.DISALLOW) or the hint hibernate.query.followOnLocking=false, commonly set when locking plus pagination is used.","commonSituations":"Generic repository layers that apply pessimistic locking to every lookup query; reporting queries built from unions that later gain a lock; lock + setMaxResults flows where follow-on locking was deliberately disabled.","solutions":["Remove the lock from the union query and lock the selected ids in a separate simple query","Rewrite the set operation as a single query spec or a join so no QueryGroup is present","Allow follow-on locking (drop the DISALLOW strategy/hint) so Hibernate falls back to locking after fetch","Run the locked read as a native query that locks a derived table TimesTen accepts"],"exampleFix":"// before\nTypedQuery<Long> q = em.createQuery(\n    \"select a.id from A a union select b.id from B b\", Long.class);\nq.setLockMode(LockModeType.PESSIMISTIC_WRITE);\nq.setHint(\"hibernate.query.followOnLocking\", false); // -> throws\n\n// after: fetch ids unlocked, then lock them\nList<Long> ids = em.createQuery(\n    \"select a.id from A a union select b.id from B b\", Long.class).getResultList();\nList<A> locked = em.createQuery(\"select a from A a where a.id in :ids\", A.class)\n    .setParameter(\"ids\", ids)\n    .setLockMode(LockModeType.PESSIMISTIC_WRITE)\n    .getResultList();","handlingStrategy":"try-catch","validationCode":"// Before locking on TimesTen, verify the query has no set operators\nstatic boolean safeToLock(String hql, Dialect d) {\n    if (!(d instanceof TimesTenDialect)) return true;\n    String u = hql.toLowerCase(Locale.ROOT);\n    return !(u.contains(\" union \") || u.contains(\" intersect \") || u.contains(\" except \"));\n}","typeGuard":null,"tryCatchPattern":"try {\n    return q.getResultList(); // q has PESSIMISTIC_WRITE + followOnLocking=false\n} catch (IllegalQueryOperationException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"set operators\")) {\n        List<Long> ids = unlockedUnionQuery();           // 1. fetch ids without lock\n        return lockByIds(ids);                            // 2. lock via simple IN query\n    }\n    throw e;\n}","preventionTips":["Never apply pessimistic locks generically to reporting/union queries","On TimesTen, lock by primary key in a follow-up simple query instead of the union itself","Do not set hibernate.query.followOnLocking=false unless you know the query shape it protects"],"tags":["hibernate","timesten","pessimistic-locking","union","set-operators"],"backgroundTag":"locking-with-set-operators-unsupported","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}