如何基于条件将JPA Hibernate多子实体的抓取类型从懒加载改急加载?
按需将JPA懒加载子实体改为急加载(避免N+1查询)
实体定义
父实体:Product
public class ProductEntity extends BaseEntity { private static final long serialVersionUID = 1L; @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "product_uuid", nullable = false) private String uuid; @Column(name = "sku", nullable = true) private Long sku; @Column(name = "name", nullable = false) private String name; @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) private Set<ProductInfoEntity> infos; @OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true) private Set<ProductTaxEntity> taxes; }
子实体
- ProductInfoEntity(简称Info)
- ProductTaxEntity(简称Tax)
问题场景
仅在少数业务场景下需要急加载子实体,但此前尝试方案时总会触发大量查询(N+1问题),想知道如何在JpaRepository/CriteriaQuery/SQL任一框架中实现按需急加载且避免额外查询。
解决方案
1. 使用JpaRepository + @EntityGraph(推荐)
通过@EntityGraph指定要急加载的关联实体,底层自动生成JOIN查询,从根源避免N+1。
示例代码:
在ProductRepository中定义查询方法:
public interface ProductRepository extends JpaRepository<ProductEntity, Long> { // 同时急加载infos和taxes两个子实体 @EntityGraph(attributePaths = {"infos", "taxes"}) List<ProductEntity> findAllByIdIn(List<Long> ids); // 仅急加载infos子实体 @EntityGraph(attributePaths = {"infos"}) Optional<ProductEntity> findByUuid(String uuid); }
原理:@EntityGraph会让JPA生成LEFT JOIN FETCH语句,一次性查询出父实体和指定子实体的所有数据。
2. 使用CriteriaQuery手动构建FETCH JOIN
通过Criteria API显式指定FETCH JOIN,精准控制要加载的关联关系。
示例代码:
public List<ProductEntity> findProductsWithInfosAndTaxes(List<Long> productIds) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<ProductEntity> cq = cb.createQuery(ProductEntity.class); Root<ProductEntity> productRoot = cq.from(ProductEntity.class); // 显式指定要FETCH的子实体关联 productRoot.fetch("infos", JoinType.LEFT); productRoot.fetch("taxes", JoinType.LEFT); cq.where(productRoot.get("id").in(productIds)); // 去重:JOIN查询会因子实体数量导致父实体重复,必须加distinct cq.distinct(true); return entityManager.createQuery(cq).getResultList(); }
3. 使用原生SQL(适合复杂关联场景)
直接编写原生SQL通过JOIN一次性查询所有数据,再映射到实体类。
示例代码:
@Query(value = "SELECT p.*, pi.*, pt.* " + "FROM product p " + "LEFT JOIN product_info pi ON p.id = pi.product_id " + "LEFT JOIN product_tax pt ON p.id = pt.product_id " + "WHERE p.id IN (:ids)", nativeQuery = true) List<Object[]> findProductsWithAllChildren(@Param("ids") List<Long> ids);
可以手动将查询结果映射到ProductEntity及其子实体集合,也可以提前定义@SqlResultSetMapping实现自动映射。
内容的提问来源于stack exchange,提问作者Ashish Aggarwal
相关产品推荐
相关产品推荐

