升级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
相关产品推荐
相关产品推荐

