hibernate/hibernate-orm · error · IllegalQueryOperationException

Locking with set operators is not supported!

Error message

Locking with set operators is not supported!

What it means

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.

Source

Thrown at hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/TimesTenSqlAstTranslator.java:52

	public TimesTenSqlAstTranslator(SessionFactoryImplementor sessionFactory, Statement statement) {
		super( sessionFactory, statement );
	}

	@Override
	protected LockStrategy determineLockingStrategy(
			QuerySpec querySpec,
			Locking.FollowOn followOnStrategy) {
		if ( followOnStrategy == Locking.FollowOn.FORCE ) {
			return LockStrategy.FOLLOW_ON;
		}

		// TimesTen supports locks with aggregates but not with set operators
		// See https://docs.oracle.com/cd/E11882_01/timesten.112/e21642/state.htm#TTSQL329
		LockStrategy strategy = LockStrategy.CLAUSE;
		if ( getQueryPartStack().findCurrentFirst( part -> part instanceof QueryGroup ? part : null ) != null ) {
			if ( followOnStrategy == Locking.FollowOn.DISALLOW ) {
				throw new IllegalQueryOperationException( "Locking with set operators is not supported!" );
			}
			else if ( followOnStrategy != Locking.FollowOn.IGNORE ) {
				strategy = LockStrategy.NONE;
			}
			else {
				strategy = LockStrategy.FOLLOW_ON;
			}
		}
		return strategy;
	}

	@Override
	protected void visitSqlSelections(SelectClause selectClause) {
		renderRowsToClause( (QuerySpec) getQueryPartStack().getCurrent() );
		super.visitSqlSelections( selectClause );
	}

	@Override

View on GitHub (pinned to fad1729dce)

Solutions

  1. Remove the lock from the union query and lock the selected ids in a separate simple query
  2. Rewrite the set operation as a single query spec or a join so no QueryGroup is present
  3. Allow follow-on locking (drop the DISALLOW strategy/hint) so Hibernate falls back to locking after fetch
  4. Run the locked read as a native query that locks a derived table TimesTen accepts

Example fix

// before
TypedQuery<Long> q = em.createQuery(
    "select a.id from A a union select b.id from B b", Long.class);
q.setLockMode(LockModeType.PESSIMISTIC_WRITE);
q.setHint("hibernate.query.followOnLocking", false); // -> throws

// after: fetch ids unlocked, then lock them
List<Long> ids = em.createQuery(
    "select a.id from A a union select b.id from B b", Long.class).getResultList();
List<A> locked = em.createQuery("select a from A a where a.id in :ids", A.class)
    .setParameter("ids", ids)
    .setLockMode(LockModeType.PESSIMISTIC_WRITE)
    .getResultList();
Defensive patterns

Strategy: try-catch

Validate before calling

// Before locking on TimesTen, verify the query has no set operators
static boolean safeToLock(String hql, Dialect d) {
    if (!(d instanceof TimesTenDialect)) return true;
    String u = hql.toLowerCase(Locale.ROOT);
    return !(u.contains(" union ") || u.contains(" intersect ") || u.contains(" except "));
}

Try / catch

try {
    return q.getResultList(); // q has PESSIMISTIC_WRITE + followOnLocking=false
} catch (IllegalQueryOperationException e) {
    if (e.getMessage() != null && e.getMessage().contains("set operators")) {
        List<Long> ids = unlockedUnionQuery();           // 1. fetch ids without lock
        return lockByIds(ids);                            // 2. lock via simple IN query
    }
    throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/a0a339f9627e2ac6. Report an issue: GitHub.