You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Hibernate 6.6.29.Final子查询关联主表时出现Could not locate TableGroup错误的解决方法咨询

Hibernate 6.6.29.Final子查询关联主表时出现Could not locate TableGroup错误的解决方法咨询

看起来你是在尝试用Hibernate Criteria API构造一个带LISTAGG的关联子查询,但因为子查询直接引用主查询的Root对象,导致了TableGroup找不到的错误。我来帮你分析问题原因,再给出几种可行的修复方案和替代写法。

错误原因分析

这个报错的核心问题是:你在嵌套子查询sqCaseNumberInnerwhere条件中直接使用了主查询的root.get(LawCardBgEntity_.id)。Hibernate的Criteria API中,子查询和主查询属于完全独立的查询上下文(TableGroup),嵌套子查询无法直接访问主查询Root对应的表组信息,因此抛出了“Could not locate TableGroup”的异常。

可行的修复/替代方案

方案一:用@Formula注解直接映射原生SQL(最简便)

如果你的需求是给LawCardBgEntity实体增加一个聚合后的caseNumber字段,最省心的方式是用Hibernate的@Formula注解,直接把你写的原生SQL片段嵌入实体类中,完全绕开Criteria API的上下文问题。

示例代码:

@Entity
@Table(name = "law_card_bg")
public class LawCardBgEntity {
    // 其他字段和主键...

    @Formula("(" +
            "SELECT LISTAGG(la.case_number, '; ') WITHIN GROUP (ORDER BY la.case_number) " +
            "FROM ( " +
            "   SELECT DISTINCT la.case_number " +
            "   FROM law_act_bg la " +
            "   LEFT JOIN law_act_bg_responsible lr ON lr.r_act_id = la.id AND NVL(lr.flag_no_work, 0) = 0 " +
            "   LEFT JOIN corp_users cu ON cu.cu_cu_id = lr.r_user_id " +
            "   LEFT JOIN dict d11 ON d11.parent_id = 11 AND d11.code = la.status " +
            "   WHERE la.r_card_bg_id = id " +  // 这里的id是当前LawCardBgEntity的主键,@Formula自动识别
            "   AND NVL(la.status, 0) <> 15 " +
            "   AND la.case_number IS NOT NULL " +
            ") la " +
            ")")
    private String caseNumber;

    // getter and setter...
}

这样你查询LawCardBgEntity时,caseNumber字段会自动通过原生SQL计算出聚合后的拼接值,不需要写任何Criteria API代码。

方案二:调整Criteria API写法,避免多层嵌套子查询

如果你一定要用Criteria API实现,可以去掉内层的嵌套子查询,直接在关联子查询中完成去重和聚合,同时避免直接在子查询中引用主查询Root。

示例代码:

// 主查询Root
Root<LawCardBgEntity> root = cq.from(LawCardBgEntity.class);

// 构造关联子查询:聚合当前LawCardBgEntity的符合条件的caseNumber
Subquery<String> caseNumberSubquery = cq.subquery(String.class);
Root<LawActBgEntity> laSubRoot = caseNumberSubquery.from(LawActBgEntity.class);

// 子查询的select:用LISTAGG聚合去重后的caseNumber
caseNumberSubquery.select(
        cb.function(
                "LISTAGG",
                String.class,
                laSubRoot.get(LawActBgEntity_.caseNumber),
                cb.literal("; ")
        )
)
// 子查询的分组(因为LISTAGG是聚合函数)
.groupBy(laSubRoot.get(LawActBgEntity_.rCardBgId))
// 子查询的过滤条件
.where(cb.and(
        cb.equal(laSubRoot.get(LawActBgEntity_.rCardBgId), root.get(LawCardBgEntity_.id)),
        cb.notEqual(cb.coalesce(laSubRoot.get(LawActBgEntity_.statusCode), cb.literal(0L)), cb.literal(15L)),
        cb.isNotNull(laSubRoot.get(LawActBgEntity_.caseNumber))
));

// 主查询的结果投影
cq.multiselect(
        root.get(LawCardBgEntity_.id),
        caseNumberSubquery.getSelection().alias("case_number")
);

注意:如果你的Oracle版本支持LISTAGG(DISTINCT ...),可以直接在cb.function的参数中传入cb.distinct(laSubRoot.get(LawActBgEntity_.caseNumber))来实现去重,不需要额外的子查询嵌套。

方案三:用原生SQL查询+结果映射(最灵活)

如果上面的写法还是有问题,直接执行你写的原生SQL,用@SqlResultSetMapping把结果映射到DTO类,是最稳妥的方式。

步骤1:定义DTO类和结果映射

@SqlResultSetMapping(
        name = "LawCardBgWithCaseNumberMapping",
        classes = @ConstructorResult(
                targetClass = LawCardBgWithCaseNumberDto.class,
                columns = {
                        @ColumnResult(name = "id", type = Long.class),
                        @ColumnResult(name = "case_number", type = String.class)
                }
        )
)
public class LawCardBgWithCaseNumberDto {
    private Long id;
    private String caseNumber;

    public LawCardBgWithCaseNumberDto(Long id, String caseNumber) {
        this.id = id;
        this.caseNumber = caseNumber;
    }

    // getters...
}

步骤2:执行原生SQL查询

String nativeSql = """
        SELECT lc.id,
               (
                   SELECT LISTAGG(la.case_number, '; ') WITHIN GROUP (ORDER BY la.case_number)
                   FROM (
                       SELECT DISTINCT la.case_number
                       FROM law_act_bg la
                       LEFT JOIN law_act_bg_responsible lr ON lr.r_act_id = la.id AND NVL(lr.flag_no_work, 0) = 0
                       LEFT JOIN corp_users cu ON cu.cu_cu_id = lr.r_user_id
                       LEFT JOIN dict d11 ON d11.parent_id = 11 AND d11.code = la.status
                       WHERE la.r_card_bg_id = lc.id
                       AND NVL(la.status, 0) <> 15
                       AND la.case_number IS NOT NULL
                   ) la
               ) AS case_number
        FROM law_card_bg lc
        """;

List<LawCardBgWithCaseNumberDto> result = entityManager.createNativeQuery(nativeSql, "LawCardBgWithCaseNumberMapping")
        .getResultList();

方案四:用主查询关联+聚合(依赖数据库支持)

如果你的数据库(如Oracle 12c R2+)支持LISTAGG(DISTINCT),可以直接在主查询中关联LawActBgEntity,然后用聚合函数完成拼接,不需要子查询。

示例代码:

Root<LawCardBgEntity> root = cq.from(LawCardBgEntity.class);

// 关联LawActBgEntity,添加过滤条件
Join<LawCardBgEntity, LawActBgEntity> laJoin = root.join(LawCardBgEntity_.lawActBgList, JoinType.LEFT);
laJoin.on(cb.and(
        cb.notEqual(cb.coalesce(laJoin.get(LawActBgEntity_.statusCode), cb.literal(0L)), cb.literal(15L)),
        cb.isNotNull(laJoin.get(LawActBgEntity_.caseNumber))
));

// 主查询投影:用LISTAGG(DISTINCT)聚合
cq.multiselect(
        root.get(LawCardBgEntity_.id),
        cb.function(
                "LISTAGG",
                String.class,
                cb.distinct(laJoin.get(LawActBgEntity_.caseNumber)),
                cb.literal("; ")
        ).alias("case_number")
)
.groupBy(root.get(LawCardBgEntity_.id));

总结

  • 优先推荐方案一(@Formula):代码最简洁,完全避开Criteria API的上下文问题
  • 如果必须用Criteria API,推荐方案二,调整子查询结构避免跨上下文引用
  • 追求最大灵活性的话,**方案三(原生SQL)**是最稳妥的选择,完全按照你自己写的SQL执行,不会有Hibernate的语法限制

火山引擎 最新活动