You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

升级Spring Boot3+Hibernate6后SqmSubQuery重复注册错误求解

解决方案:Spring Boot 3 + Hibernate 6 计数查询重复注册SQM节点错误

错误原因

直接复用原CriteriaQuery中的Root、Predicate、分组列表等对象是问题核心——这些对象已经和原查询的SQM(语义查询模型)树绑定,Hibernate 6严格禁止将同一个SQM节点(比如子查询节点)注册到多个不同的查询实例中,执行em.createQuery(countCriteria)时就会触发重复注册异常。

修复方案

方案1:手动复制查询元素(纯JPA兼容)

重新构建计数查询的所有节点,避免复用原查询对象,通过映射表替换原查询中的Root引用:

import jakarta.persistence.criteria.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public static <T> Long getRows(final EntityManager em, final CriteriaQuery<T> criteria) {
    final CriteriaBuilder cb = em.getCriteriaBuilder();
    final CriteriaQuery<Long> countCriteria = cb.createQuery(Long.class);
    
    // 构建原Root与新Root的映射,避免复用原节点
    final Map<Root<?>, Root<?>> rootMapping = new HashMap<>();
    for (Root<?> originalRoot : criteria.getRoots()) {
        Root<?> newRoot = countCriteria.from(originalRoot.getJavaType());
        newRoot.alias(originalRoot.getAlias());
        // 复制原Root的关联关系
        copyJoinRelations(newRoot, originalRoot);
        rootMapping.put(originalRoot, newRoot);
    }

    // 设置count查询的select子句,支持distinct
    Root<?> countRoot = rootMapping.values().iterator().next();
    if (criteria.isDistinct()) {
        countCriteria.select(cb.countDistinct(countRoot));
    } else {
        countCriteria.select(cb.count(countRoot));
    }

    // 转换并设置where条件
    Predicate originalWhere = criteria.getRestriction();
    if (originalWhere != null) {
        countCriteria.where(translatePredicate(originalWhere, rootMapping, cb));
    }

    // 转换并设置分组和having条件
    List<Expression<?>> originalGroups = criteria.getGroupList();
    if (!originalGroups.isEmpty()) {
        List<Expression<?>> newGroups = new ArrayList<>();
        for (Expression<?> expr : originalGroups) {
            newGroups.add(translateExpression(expr, rootMapping, cb));
        }
        countCriteria.groupBy(newGroups);
        
        Predicate originalHaving = criteria.getGroupRestriction();
        if (originalHaving != null) {
            countCriteria.having(translatePredicate(originalHaving, rootMapping, cb));
        }
    }

    countCriteria.distinct(criteria.isDistinct());
    
    return em.createQuery(countCriteria).getSingleResult();
}

// 复制Join关联关系
private static <X, Y> void copyJoinRelations(Root<X> newRoot, Join<X, Y> originalJoin) {
    Join<X, Y> newJoin = newRoot.join(originalJoin.getAttribute().getName(), originalJoin.getJoinType());
    newJoin.alias(originalJoin.getAlias());
    // 递归复制子Join
    for (Join<?, ?> subJoin : originalJoin.getJoins()) {
        copyJoinRelations(newJoin, subJoin);
    }
}

// 转换表达式中的Root引用
@SuppressWarnings("unchecked")
private static <E> Expression<E> translateExpression(Expression<E> expr, Map<Root<?>, Root<?>> rootMapping, CriteriaBuilder cb) {
    if (expr instanceof Root<?>) {
        return (Expression<E>) rootMapping.get(expr);
    } else if (expr instanceof Join<?, ?>) {
        Join<?, ?> originalJoin = (Join<?, ?>) expr;
        Root<?> originalRoot = (Root<?>) originalJoin.getParent();
        Root<?> newRoot = rootMapping.get(originalRoot);
        return (Expression<E>) newRoot.join(originalJoin.getAttribute().getName(), originalJoin.getJoinType());
    } else if (expr instanceof CompoundPredicate) {
        CompoundPredicate cp = (CompoundPredicate) expr;
        List<Predicate> translatedPredicates = new ArrayList<>();
        for (Predicate p : cp.getExpressions()) {
            translatedPredicates.add(translatePredicate(p, rootMapping, cb));
        }
        return cp.getOperator() == CompoundPredicate.BooleanOperator.AND ? cb.and(translatedPredicates.toArray(new Predicate[0])) : cb.or(translatedPredicates.toArray(new Predicate[0]));
    }
    // 字面量、函数等表达式直接返回
    return expr;
}

// 转换Predicate中的Root引用
private static Predicate translatePredicate(Predicate predicate, Map<Root<?>, Root<?>> rootMapping, CriteriaBuilder cb) {
    if (predicate instanceof SimplePredicate) {
        SimplePredicate sp = (SimplePredicate) predicate;
        Expression<?> left = translateExpression(sp.getExpressions().get(0), rootMapping, cb);
        Expression<?> right = translateExpression(sp.getExpressions().get(1), rootMapping, cb);
        
        return switch (sp.getOperator()) {
            case EQUAL -> cb.equal(left, right);
            case NOT_EQUAL -> cb.notEqual(left, right);
            case GREATER_THAN -> cb.greaterThan((Expression<? extends Comparable>) left, (Expression<? extends Comparable>) right);
            case GREATER_THAN_OR_EQUAL -> cb.greaterThanOrEqualTo((Expression<? extends Comparable>) left, (Expression<? extends Comparable>) right);
            case LESS_THAN -> cb.lessThan((Expression<? extends Comparable>) left, (Expression<? extends Comparable>) right);
            case LESS_THAN_OR_EQUAL -> cb.lessThanOrEqualTo((Expression<? extends Comparable>) left, (Expression<? extends Comparable>) right);
            default -> predicate;
        };
    } else if (predicate instanceof CompoundPredicate) {
        CompoundPredicate cp = (CompoundPredicate) predicate;
        List<Predicate> translatedPredicates = new ArrayList<>();
        for (Predicate p : cp.getExpressions()) {
            translatedPredicates.add(translatePredicate(p, rootMapping, cb));
        }
        return cp.getOperator() == CompoundPredicate.BooleanOperator.AND ? cb.and(translatedPredicates.toArray(new Predicate[0])) : cb.or(translatedPredicates.toArray(new Predicate[0]));
    }
    return predicate;
}

方案2:使用Hibernate内置工具(简洁高效)

如果项目允许直接使用Hibernate的API,可以利用HibernateCriteriaBuilder的内置转换逻辑,避免手动复制:

import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaQuery;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;

public static <T> Long getRows(final EntityManager em, final CriteriaQuery<T> criteria) {
    HibernateCriteriaBuilder hcb = em.getCriteriaBuilder().unwrap(HibernateCriteriaBuilder.class);
    JpaCriteriaQuery<Long> countQuery = (JpaCriteriaQuery<Long>) hcb.createCountQuery(criteria);
    return em.createQuery(countQuery).getSingleResult();
}

这个方法通过Hibernate内置的转换器自动处理查询节点的复制,大幅减少代码量且不易出错。

内容的提问来源于stack exchange,提问作者vaps

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 15:07:17