Spring Data JPA入门记录(三)解决懒加载N+1问题

4 阅读2分钟

一、问题介绍

fetch = FetchType.LAZY 懒加载,使用时才加载

fetch = FetchType.EAGER 立即加载

当实体类中存在@OneToMany(fetch = FetchType.LAZY)注解时,表示这个字段使用的是懒加载方式。这种方式在查询时不会真正调用SQL,只有在后续使用时才会调用。例如Product实体类中存在一对多的字段parameters

@Entity  
public class Product {  
  
    @OneToMany(mappedBy = "product", fetch = FetchType.LAZY)  
    private List<Parameter> parameters;  
}

当你查询product时,并不会调用查询parameters的SQL语句,只有在使用product时才会调用:

product.getParameters().size(); //此时调用SQL语句查询parameters,但会调用N+1此SQL

此时调用N+1次SQL完成parameters的查询,消耗大量时间,因此有些情况下,我们希望在查询product时就把parameters也查询出来。这个解决方法便是实体图。

二、实体图 Entity Graph

实体图可以指定如何获取实体以及实体关联的实体。在Srping Data JPA中提供了 @NamedEntityGraph 和 @EntityGraph 注解用于定义和使用实体图。

此处参考博客# 使用EntityGraph解决JPA下N+1问题 中的示例,假设目前的问题为:A--oneToMany-->B--oneToMany-->C,如果需要通过实体图完成一次性加载,需要两步:

  1. 在实体类A上添加 @NamedEntityGraph 注解,用于定义实体图:
@NamedEntityGraph(
        name = "aWithBWithC",
        attributeNodes = {
                @NamedAttributeNode(value = "bs", subgraph = "bWithC")},
        subgraphs = {
                @NamedSubgraph(name = "bWithC",
                        attributeNodes = {@NamedAttributeNode("cs")})
        }
)
@Entity
@Table(name = "a", catalog = "funny", schema = "testing")
public class A {
    @OneToMany(fetch = FetchType.LAZY)
    @JoinTable( //if you have a table a_b for this relationship. Not related to the topic of entityGraph though. 
            name = "a_b", 
            joinColumns = @JoinColumn(name = "a_id"),
            inverseJoinColumns = @JoinColumn(name = "b_id")
    )
    private Set<B> bs;
}
  • name = "aWithBWithC"定义实体图名称。
  • attributeNodes = 表示当前层要加载的内容。
  • @NamedAttributeNode(value = "bs", subgraph = "bWithC")用于定义要加载的字段,字段为bs,使用bWithC这个分实体图加载。
    • value = 定义分实体图名称
    • subgraphs = 表示分实体图的信息,其中可以定义多个分实体图,分实体图由 @NamedSubgraph 表示,内容和 @NamedEntityGraph 相同。
  1. 然后在定义查询语句时,增加 @EntityGraph 注解:
public interface AsRepository extends PagingAndSortingRepository<A, Long> {


    @EntityGraph(value = "aWithBWithC", type = EntityGraph.EntityGraphType.FETCH)
    Page<Item> findAll(Pageable pageable); //FETCH will load all nested entity in one query. 
}

@EntityGraph 的type字段有两个取值:

EntityGraph.EntityGraphType.FETCH //只有graph中指定的属性立即加载,其他所有属性强制懒加载
EntityGraph.EntityGraphType.LOAD //graph中指定的属性立即加载,其他属性保持原有加载策略

此时进行findAll查询时,就会自动将B和C也加载进来。