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

Hibernate Criteria继承构造DTO时出现关联获取异常问题

问题排查与解决思路

我之前也碰到过类似的Hibernate Criteria查询DTO的坑,咱们来一步步理清问题根源和解决办法:

异常原因分析

这个org.hibernate.QueryException的核心问题在于DTO继承实体类的方式和Hibernate Criteria的投影逻辑不兼容,结合你的场景具体来说:

  1. 你的InventoryRequestDTO继承了实体类InventoryRequest,而InventoryRequest又继承了带@MappedSuperclass的AuditEntity。但Hibernate并不会把继承实体的DTO当成“可映射的实体”来处理——当你用Criteria查询返回DTO时,必须明确指定要投影的字段,而不是依赖实体的继承关系自动映射。
  2. 你的SpecificationBuilder会处理organization、created_by、modified_by这些属性,其中created_by/modified_by大概率是@ManyToOne这类关联属性。如果Specification里用了fetch join(比如root.fetch("createdBy"))来加载关联实体,Hibernate会要求“关联的所有者(也就是InventoryRequest主实体)必须在选择列表中”,但你现在查询的是DTO,没有把主实体本身加入投影列表,就触发了这个异常。
  3. 用原InventoryRequest查询正常的原因是:它是Hibernate管理的实体类,Criteria返回实体时,主实体默认就在选择列表里,Hibernate可以自动处理关联的fetch逻辑,不会有冲突。

具体解决方法

方法1:改用独立DTO+显式投影(最稳妥)

放弃让DTO继承实体类,定义一个独立的InventoryRequestDTO,包含所有需要的字段(包括AuditEntity里的属性),然后在Criteria中手动指定投影字段,用ResultTransformer转换结果:

// 独立DTO示例
public class InventoryRequestDTO {
    private Long id;
    private String organization;
    private User createdBy; // 或者只保留需要的字段,比如String createdByUsername
    // 其他业务字段...
    // 全参/无参构造、getter/setter
}

// Criteria查询代码
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<InventoryRequestDTO> cq = cb.createQuery(InventoryRequestDTO.class);
Root<InventoryRequest> root = cq.from(InventoryRequest.class);

// 构建投影列表,映射到DTO的字段
ProjectionList projections = Projections.projectionList()
    .add(Projections.property("id"), "id")
    .add(Projections.property("organization"), "organization")
    .add(Projections.property("createdBy"), "createdBy")
    // 添加上所有需要的业务字段
    .add(Projections.property("requestQuantity"), "requestQuantity");

cq.select(projections);
// 应用Specification条件
Specification<InventoryRequest> spec = auditSpecificationBuilder.build();
cq.where(spec.toPredicate(root, cq, cb));

// 设置分页
cq.setFirstResult(pageable.getPageNumber() * pageable.getPageSize());
cq.setMaxResults(pageable.getPageSize());

// 转换结果为DTO
List<InventoryRequestDTO> results = entityManager.createQuery(cq)
    .setResultTransformer(Transformers.aliasToBean(InventoryRequestDTO.class))
    .getResultList();

注意:DTO的字段名要和投影的别名完全一致,或者用@Alias注解指定映射关系。

方法2:调整Specification中的关联处理(如果坚持DTO继承实体)

如果一定要让DTO继承实体类,需要修改SpecificationBuilder中的关联逻辑:

  • 把fetch join改成普通join,避免Hibernate自动加载关联实体;
  • 手动投影关联实体的需要字段,而不是依赖fetch来加载整个关联对象。
    比如把原来的:
root.fetch("createdBy", JoinType.LEFT);

改成:

Join<InventoryRequest, User> createdByJoin = root.join("createdBy", JoinType.LEFT);
// 投影createdBy的具体字段到DTO对应的属性
projections.add(Projections.property(createdByJoin.get("username")), "createdByUsername");

这样就不会触发“关联所有者不在选择列表”的异常,因为你没有用fetch来强制加载关联实体,而是手动投影需要的字段。

方法3:使用Spring Data JPA Projection接口(最简洁,适用于Spring环境)

如果你的项目用了Spring Data JPA,可以定义一个Projection接口来替代DTO,Spring会自动处理投影逻辑:

// 定义Projection接口,包含所有需要的getter方法
public interface InventoryRequestProjection {
    Long getId();
    String getOrganization();
    // 可以直接返回关联对象,或者只取需要的字段
    String getCreatedByUsername(); // 假设User实体有getUsername()方法
    // 其他业务字段的getter...
}

// 在Repository中添加方法
public interface InventoryRequestRepository extends JpaRepository<InventoryRequest, Long>, JpaSpecificationExecutor<InventoryRequest> {
    Page<InventoryRequestProjection> findAll(Specification<InventoryRequest> spec, Pageable pageable);
}

这种方式不需要自己写Criteria代码,Spring会自动构建正确的查询,避免手动处理投影的麻烦,也不会出现连接获取的异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 13:42:56