使用EntityGraph查询含可选ManyToOne关联的Hibernate实体问题
问题描述
我有一个Hibernate实体Expense,它与Shipment是可选关联:
@NamedEntityGraph( name = "expense-for-frontend", attributeNodes = { @NamedAttributeNode("shipment"), }) @Entity public class Expense { ... @ManyToOne(fetch = FetchType.LAZY, optional = true) private Shipment shipment; ... }
需求是加载所有Expense实体(无论是否关联Shipment),同时预加载关联的Shipment以避免N+1查询。仓库中的查询如下:
@EntityGraph("expense-for-frontend") @Query( """ SELECT e FROM Expense e LEFT JOIN Shipment s ON e.shipment.id = s.id WHERE (:status IS NULL OR e.status = :status) """) List<PaidShipmentExpenseBE> findAllFilteredShipmentExpensesWithPayment( @Param("status") @Nullable VendorPaymentStatus status);
但执行后生成的SQL中,EntityGraph自动添加了对shipment的内连接,导致无法获取未关联Shipment的Expense:
select eb1_0.id, ... from expense eb1_0 join shipment s1_0 on s1_0.id=eb1_0.shipment_id left join shipment sb1_0 on sb1_0.tenant_id = ? and eb1_0.shipment_id=sb1_0.id ...
其中s1_0是EntityGraph添加的无用内连接,现在面临两难:
- 移除
@NamedAttributeNode("shipment")会导致N+1查询,数据量大时性能极差; - 找不到配置
NamedAttributeNode生成左连接的方式。
明明已经用@ManyToOne(optional = true)声明了关联可选,EntityGraph却没有正确处理,想知道是否有遗漏的配置方式。
解决方案
方法一:用JPQL LEFT JOIN FETCH替代EntityGraph
直接在JPQL查询中使用LEFT JOIN FETCH,既能保证左连接获取所有Expense,又能预加载Shipment避免N+1,完全不需要依赖EntityGraph:
@Query( """ SELECT e FROM Expense e LEFT JOIN FETCH e.shipment s WHERE (:status IS NULL OR e.status = :status) """) List<PaidShipmentExpenseBE> findAllFilteredShipmentExpensesWithPayment( @Param("status") @Nullable VendorPaymentStatus status);
这种方式是最直接可靠的,完全符合需求,不需要额外配置。
方法二:配置EntityGraph使用左连接(Hibernate扩展)
如果一定要用EntityGraph,可以通过Hibernate的JoinHint扩展,强制EntityGraph对shipment使用左连接:
@NamedEntityGraph( name = "expense-for-frontend", attributeNodes = { @NamedAttributeNode(value = "shipment", subgraph = "left-join-shipment") }, subgraphs = { @NamedSubgraph( name = "left-join-shipment", attributeNodes = {}, joinHint = @JoinHint(name = "javax.persistence.fetchgraph.join.type", value = "LEFT") ) } )
注意这个特性是Hibernate专属的,需要Hibernate 5.2及以上版本支持。
方法三:检查全局租户过滤配置
生成的SQL中出现了tenant_id = ?的条件,大概率是全局启用了Hibernate租户过滤器。如果过滤器对Shipment表设置了强制非空的过滤规则,可能会导致EntityGraph自动生成内连接。可以尝试:
- 在仓库方法上添加
@FilterDisable注解(如果使用了Hibernate Filter),临时禁用过滤器; - 调整租户过滤器的配置,允许
Shipment关联为空的情况。
内容的提问来源于stack exchange,提问作者Kevin
相关产品推荐
相关产品推荐

