如何在Spring Data JPA中默认使用@EntityGraph加载实体避免N+1问题
针对FetchType.EAGER触发N+1查询,需要实体查询时默认自动应用指定EntityGraph、无需每个Repository方法单独加@EntityGraph注解的需求,有三种常用实现方案:
方案1:Repository接口全局绑定EntityGraph
Spring Data JPA 1.10及以上版本支持直接在*Repository接口上标注@EntityGraph注解,接口内所有查询方法(包括框架自带的findAll、findById等默认方法,以及自定义查询方法)都会自动应用该EntityGraph:
@EntityGraph(value = Restaurant.GRAPH_RESTAURANT_WITH_VOTES, type = EntityGraph.EntityGraphType.FETCH) public interface RestaurantRepository extends JpaRepository<Restaurant, Integer> { // 所有方法默认自动应用上方配置的EntityGraph // 个别方法需要特殊加载策略时,单独加@EntityGraph即可覆盖全局配置 }
该方案实现最简单,适合单个Repository的所有查询都使用同一种关联加载策略的场景。
方案2:自定义基础Repository实现类,全局自动匹配EntityGraph
如果需要多实体统一配置,不需要每个Repository单独加注解,可以自定义Spring Data JPA的基础Repository实现,自动识别实体上标注的@NamedEntityGraph并默认应用:
- 定义基础Repository接口
@NoRepositoryBean public interface BaseRepository<T, ID> extends JpaRepository<T, ID> { }
- 实现基础Repository逻辑,自动注入默认EntityGraph
public class BaseRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> { private final EntityManager entityManager; private final Class<T> domainClass; public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) { super(entityInformation, entityManager); this.entityManager = entityManager; this.domainClass = entityInformation.getJavaType(); } @Override protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) { TypedQuery<S> query = super.getQuery(spec, domainClass, sort); applyDefaultEntityGraph(query); return query; } private <S> void applyDefaultEntityGraph(TypedQuery<S> query) { // 检测实体类是否标注了NamedEntityGraph NamedEntityGraph defaultGraph = domainClass.getAnnotation(NamedEntityGraph.class); if (defaultGraph != null) { EntityGraph<?> entityGraph = entityManager.getEntityGraph(defaultGraph.name()); // FETCH类型加载用fetchgraph,LOAD类型用loadgraph query.setHint("javax.persistence.fetchgraph", entityGraph); } } }
- 启动类指定自定义Repository实现
@SpringBootApplication @EnableJpaRepositories(repositoryBaseClass = BaseRepositoryImpl.class) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
配置完成后,所有标注了@NamedEntityGraph的实体,调用对应Repository的任意查询方法都会自动应用配置的关联加载策略。
方案3:Hibernate专属@Fetch注解(简单场景适用)
如果你使用的ORM框架是Hibernate,不需要兼容其他JPA实现,可以直接在关联属性上加@Fetch(FetchMode.JOIN),无需配置EntityGraph也能避免N+1问题:
@OneToMany(mappedBy = "restaurant", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) @JsonManagedReference @Fetch(FetchMode.JOIN) private List<Vote> votes;
注意:该方案仅对Hibernate生效,查询多条实体时会生成笛卡尔积,需要搭配distinct使用避免返回重复数据。
内容的提问来源于stack exchange,提问作者user15599360
相关产品推荐
相关产品推荐

