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

Hibernate 6报错:已注册副本SqmBasicValuedSimplePath

Hibernate 6复制Criteria构建计数查询触发"Already registered a copy"错误的解决办法

问题场景

基于现有Criteria复制构建用于统计计数的Criteria时,Hibernate 6抛出如下错误,相同代码在Hibernate 5中可正常运行:

Caused by: java.lang.IllegalArgumentException: Already registered a copy:

SqmBasicValuedSimplePath(com.example.domain.Test(175781908930100).name)

问题原因

Hibernate 6的SQM(语义查询模型)对查询元素的上下文管理机制比Hibernate 5更严格。原代码直接将原Criteria中的Predicate(包含绑定到原Root/Join的路径表达式)复用给新的计数Criteria,但新Criteria已经创建了独立的Root实例,原路径属于旧的查询上下文,无法在新上下文中重复注册,因此触发重复注册的错误。

解决方案

需要在复制查询条件时,将原Predicate、GroupBy、Having中的路径表达式替换为新Criteria对应的Root/Join实例,确保所有表达式都属于新的查询上下文。具体改造如下:

1. 新增表达式替换工具方法

用于遍历表达式树,将原From对象(Root/Join)替换为新的对应实例:

private static Expression<?> replaceExpression(Expression<?> original, Map<From<?,?>, From<?,?>> fromMap) {
    if (original instanceof From<?,?>) {
        return fromMap.get(original);
    }
    if (original instanceof Predicate) {
        Predicate predicate = (Predicate) original;
        Predicate.BooleanOperator operator = predicate.getOperator();
        List<Expression<?>> replacedExpressions = new ArrayList<>();
        for (Expression<?> expr : predicate.getExpressions()) {
            replacedExpressions.add(replaceExpression(expr, fromMap));
        }
        if (operator == Predicate.BooleanOperator.AND) {
            return predicate.getBuilder().and(replacedExpressions.toArray(new Expression[0]));
        } else if (operator == Predicate.BooleanOperator.OR) {
            return predicate.getBuilder().or(replacedExpressions.toArray(new Expression[0]));
        } else {
            return predicate.getBuilder().not((Predicate) replacedExpressions.get(0));
        }
    }
    if (original instanceof Path<?>) {
        Path<?> path = (Path<?>) original;
        Expression<?> parent = replaceExpression(path.getParentPath(), fromMap);
        if (parent instanceof Path<?>) {
            return ((Path<?>) parent).get(path.getJavaType());
        }
    }
    return original;
}

private static List<Expression<?>> replaceExpressions(List<Expression<?>> expressions, Map<From<?,?>, From<?,?>> fromMap) {
    List<Expression<?>> replaced = new ArrayList<>();
    for (Expression<?> expr : expressions) {
        replaced.add(replaceExpression(expr, fromMap));
    }
    return replaced;
}

2. 改造复制Criteria的方法

维护原From到新From的映射,替换所有表达式后再设置到新Criteria中:

private static void copyCriteriaWithoutSelectionAndOrder(
        CriteriaQuery<?> from, CriteriaQuery<?> to) {
    Map<From<?,?>, From<?,?>> fromMap = new HashMap<>();

    // 复制Root并建立映射
    for (Root<?> root : from.getRoots()) {
        Root<?> dest = to.from(root.getJavaType());
        dest.alias(getOrCreateAlias(root));
        fromMap.put(root, dest);
        copyJoins(root, dest, fromMap);
    }

    // 替换GroupBy表达式
    to.groupBy(replaceExpressions(from.getGroupList(), fromMap));
    to.distinct(from.isDistinct());

    // 替换Having表达式
    if (from.getGroupRestriction() != null) {
        Predicate replacedHaving = (Predicate) replaceExpression(from.getGroupRestriction(), fromMap);
        to.having(replacedHaving);
    }

    // 替换Where条件表达式
    Predicate predicate = from.getRestriction();
    if (predicate != null) {
        Predicate replacedPredicate = (Predicate) replaceExpression(predicate, fromMap);
        to.where(replacedPredicate);
    }
}

private static void copyJoins(From<?,?> from, From<?,?> to, Map<From<?,?>, From<?,?>> fromMap) {
    for (Join<?,?> join : from.getJoins()) {
        Join<?,?> destJoin = to.join(join.getAttribute().getName(), join.getJoinType());
        destJoin.alias(getOrCreateAlias(join));
        fromMap.put(join, destJoin);
        copyJoins(join, destJoin, fromMap);
    }
}

// 保留原有的别名获取方法
private static String getOrCreateAlias(Root<?> root) {
    String alias = root.getAlias();
    return alias != null ? alias : "root_" + System.identityHashCode(root);
}

3. 其他代码保持不变

原有的getResultsPage、getResultList、count、countCriteria方法无需修改。

效果说明

改造后的代码通过上下文映射替换所有路径表达式,确保新的计数Criteria使用自身的Root/Join实例,避免了Hibernate 6中严格的SQM注册机制触发的重复注册错误,同时兼容Hibernate 5的运行逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 23:35:42