延迟加载 与 N+1

JPA 中默认 @ManyToMany 和 @OneToMany 使用延迟加载

其抓取方式为 Feth.select 会产生 N+1 条查询语句

这使得对数据库产生很大的压力

使用立即加载 则不会有 N+1 的问题,单多表关联时,即使仅仅查询某一个表中的数据

仍然会采用多表关联查询,也会对数据库产生压力

解决方案

  1. 使用 JPA 2.1 的 Name的 @NameEntityGraph @EntityGraph 来动态进行动态抓取

  2. 使用 JPQL 的 Fetch 语句 或者 单独使用 @EntityGraph ,对一层嵌套进行强制抓取

FetchType 与 @Fetch 比较:

两者都是设定关联对象的加载策略。前者是JPA标准的通用加载策略注解属性,后者是Hibernate自有加载策略注解属性。

FetchType可选值意义与区别如下:

FetchType.LAZY: 懒加载,在访问关联对象的时候加载(即从数据库读入内存)

FetchType.EAGER:立刻加载,在查询主对象的时候同时加载关联对象。

FetchMode可选值意义与区别如下:

  • @Fetch(FetchMode.JOIN): 始终立刻加载,使用外连(outer join)查询的同时加载关联对象,忽略FetchType.LAZY设定,只产生一条sql语句。

  • @Fetch(FetchMode.SELECT): 默认懒加载(除非设定关联属性lazy=false),当访问每一个关联对象时加载该对象,会累计产生N+1条sql语句,FetchType.LAZY设定时默认使用。

  • @Fetch(FetchMode.SUBSELECT): 默认懒加载(除非设定关联属性lazy=false),在访问第一个关联对象时加载所有的关联对象。会累计产生两条sql语句。且FetchType设定有效。

Fetch

在 Join 语句后添加 fetch 关键字,可强行抓取关联对象,生成的 sql 语句为 left outer join。

只对一层关联有效,不能在多层关联中使用。

示例:

@Query("select d from Department d join fetch d.bossList where d.id=:id")

对于 @Query 查询来说,@Query 查询受 fetch 语句 和 FetchType.LAZY/EAGER 影响 不受 @Fetch 影响

@Query 的 FetchType.EAGER 查询固定使用 select 模式 会产生 N+1 问题 且 不受 @Fetch 影响

@EntityGraph

使用该注解被声明 attributePaths 属性为关联实体属性,同样只支持一层抓取。

该属性支持一层级抓取多个关联实体属性 attributePaths = {“parm1”,”parm2”}

示例:

@EntityGraph(attributePaths = "bossList")
@Query("select d from Department d where d.id=:id")

EntityGraphType.LOAD 和 EntityGraphType.FETCH 为该注解可选属性,默认为 EntityGraphType.FETCH

EntityGraphType.LOAD:在原有Entity的定义的基础上,定义还需要获取什么字段/关系

EntityGraphType.FETCH:完全放弃原有Entity的定义,定义仅需要获取什么字段/关系

EntityGraphType.LOAD:被加载的数据为name以及employees

EntityGraphType.FETC:被加载的数据仅为employees

@NamedEntityGraphs

该注解声明在实体上,必须声明 name 属性,由接口方法中的 @EntityGraph 注解 value 属性调用,默认可省略 value

示例:

@EntityGraph("department.all")
@Query("select d from Department d where d.id=:id")

@NamedEntityGraphs 为数组声明多个 @NamedEntityGraph 注解

@NamedEntityGraph 的 name 属性为调用名 attributeNodes 为定义的抓取的节点属性,包含多个@NamedAttributeNode

@NamedAttributeNode 包含 value 和 subgraph 两个值,只有 value 时 可省略

value 为要抓取的关联实体属性 subgraph 则为要抓取的子实体名 对应为 @NamedSubgraph 的 name 属性值

subgraphs 包含多个 @NamedSubgraph 为抓取子实体关联节点

@NamedSubgraph 的 name 属性为子实体名 attributeNodes 则可定义嵌套抓取节点,

功能同 @NamedEntityGraph 的attributeNodes。可嵌套使用

下面的示例为抓取了 A 关联的实体属性 B 和 B 关联的实体属性 C 和 C 关联的实体属性 D

多层级抓取 需要使用 @IndexColumn 防止 cannot simultaneously fetch multiple bags 异常

@Entity
@Table
@NamedEntityGraphs({
@NamedEntityGraph(name = "A",
attributeNodes = {
@NamedAttributeNode(value = "B",subgraph = "B.name"),
},
subgraphs = {
@NamedSubgraph(name = "B.name", //一层延伸
attributeNodes = @NamedAttributeNode(value = "C", subgraph = "C.name")),
@NamedSubgraph(name = "C.name", //两层延伸
attributeNodes = @NamedAttributeNode(value = "D"))
})
})

Class A (){
...
}
// 使用
@EntityGraph("A")

注意

抓取超过一层的实体时,要求多实体一侧必须使用 Set 集合

当一个实体对象中包含多于一个非延迟加载策略时,比如 @OneToMany,@ManyToMany 或者 @ElementCollection 时,获取策略为(fetch = FetchType.EAGER)

当(fetch = FetchType.EAGER)多于一个时,持久框架抓取一方的对象时,同时又将多方的对象加载进容器中,多方又可能关联其它对象,

Hibernate实现的JPA,默认最高抓取深度含本身级为四级(它有个属性配置是0-3),

若多方(第二级)存在重复值,则第三级中抓取的值就无法映射,就会出现

org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags 异常。

Hibernate 解决方案

@IndexColumn(name = “二级主键”) 或者次级主键

其实也可以使用 Set 集合来去重,并配合 @OrderIndex 来实现有序抓取。

但使用Set集合不能使用索引。优点是 该方法属于 JPA 规范内

使用 @IndexColumn 指定 唯一索引 属于超出 JPA 规范的方法 为 Hibernate 的专用方法。

推荐使用 @IndexColumn