使用DTO投影和JOIN FETCH执行JPA查询时的Hibernate异常问题咨询
1. 缺失的关联所有者是谁?
这个异常里提到的“关联所有者”就是你的根实体 Ereturn。Hibernate的JOIN FETCH机制要求:你要抓取的关联(这里是e.productItems)的所有者必须出现在查询的SELECT列表中——要么是完整的Ereturn实体,要么你明确选择了该实体(这样Hibernate才能把抓取到的productItems关联到对应的所有者上)。但你的查询用了DTO投影,只选择了Ereturn的几个零散属性(rma, shipper, carrier, returnAction),并没有把Ereturn本身作为一个整体包含在SELECT里,这就导致Hibernate找不到绑定关联数据的所有者,因此抛出了这个异常。
2. 如何修改查询(兼顾DTO性能与关联需求)?
根据你的实际需求,有两种合理的修正方向:
方案一:改用普通JOIN(仅过滤数据,不需要返回productItems)
如果你的DTO只需要Ereturn的那几个字段,只是用productItems的条件过滤结果,那完全不需要JOIN FETCH——JOIN FETCH是用来提前加载关联实体避免N+1查询的,但你这里根本不需要返回productItems的数据,用普通JOIN即可,同时保留DISTINCT避免重复结果:
em.createQuery("SELECT DISTINCT new returnitRest.Ereturn(e.rma, e.shipper, e.carrier, e.returnAction) FROM Ereturn e " + "JOIN e.productItems pi " + // 去掉FETCH关键字 "WHERE e.status = 'RECEIVED' AND " + "e.shipper.email = :shipper AND " + "e.carrier.email = :carrier AND " + "pi.returnAction = :returnAction ") .setParameter("shipper", shipperEmail) .setParameter("carrier", issuer) .setParameter("returnAction", ReturnAction.valueOf(returnAction)) .getResultList();
方案二:扩展DTO包含关联数据(需要返回productItems的部分/全部字段)
如果你需要把productItems的某些数据也纳入DTO,那首先要修改DTO的构造函数,加入需要的关联字段,然后在查询的SELECT里明确选择这些内容(此时用普通JOIN即可,不需要FETCH,因为DTO投影已经直接获取了需要的字段):
比如先创建一个包含关联数据的DTO(示例命名为EreturnWithItemsDTO):
public class EreturnWithItemsDTO { private String rma; private Shipper shipper; private Carrier carrier; private ReturnAction returnAction; private List<ProductItem> productItems; // 也可以只存ProductItem的部分属性,比如List<String> itemSkus // 对应构造函数 public EreturnWithItemsDTO(String rma, Shipper shipper, Carrier carrier, ReturnAction returnAction, List<ProductItem> productItems) { this.rma = rma; this.shipper = shipper; this.carrier = carrier; this.returnAction = returnAction; this.productItems = productItems; } }
再修改查询语句:
em.createQuery("SELECT DISTINCT new returnitRest.EreturnWithItemsDTO(e.rma, e.shipper, e.carrier, e.returnAction, e.productItems) FROM Ereturn e " + "JOIN e.productItems pi " + "WHERE e.status = 'RECEIVED' AND " + "e.shipper.email = :shipper AND " + "e.carrier.email = :carrier AND " + "pi.returnAction = :returnAction ") .setParameter("shipper", shipperEmail) .setParameter("carrier", issuer) .setParameter("returnAction", ReturnAction.valueOf(returnAction)) .getResultList();
补充说明
JOIN FETCH的核心是为实体对象加载关联属性,而DTO投影是直接查询需要的字段、跳过实体对象的完整加载——这两者的设计目标存在冲突。所以当你使用DTO投影时,JOIN FETCH通常是多余的,甚至会触发Hibernate的元数据校验失败,也就是你遇到的这个异常。
内容的提问来源于stack exchange,提问作者masber

