Spring Data JPA原生查询@OneToOne关联出现N+1查询问题怎么解决?
问题原因
- JPA中
@OneToOne默认的加载策略是EAGER(立即加载),你使用原生SQL查询时,Hibernate默认不会自动把JOIN出来的Data表字段填充到Info的data关联属性里,只会提取到外键data_id的值。 - 因为关联属性配置了立即加载,Hibernate发现
data属性未填充时,就会自动发起第二次查询,根据data_id查询对应的Data实体,所以就出现了两次SQL执行的现象。 - 额外注意:
@Data注解会自动生成包含所有属性的toString、equals、hashCode方法,若代码中(包括日志打印)调用了这些方法,也可能触发关联属性的加载查询。
解决方案
方案1:改用JPQL关联抓取(推荐)
不需要使用原生查询,直接用JPQL的JOIN FETCH语法即可一次查询并填充所有关联属性,代码修改如下:
@Query("SELECT i FROM Info i JOIN FETCH i.data") List<Info> getInfo();
该方案无需额外配置,JPA会自动生成关联查询SQL,一次完成所有属性填充,不会触发二次查询。
方案2:原生SQL添加结果集映射
如果业务要求必须使用原生SQL查询,需要显式声明结果集映射,让Hibernate识别关联实体的字段映射:
第一步:在Info实体类上添加@SqlResultSetMapping配置:
@Data @Entity @Table(name = "Info") @SqlResultSetMapping( name = "InfoWithDataMap", entities = { @EntityResult(entityClass = Info.class), @EntityResult(entityClass = Data.class) } ) public class Info { @OneToOne @JoinColumn(name = "data_id") private Data data; }
第二步:修改查询方法,指定关联的结果集映射:
@Query(value = "SELECT i.*, d.* FROM Info i inner join Data d on d.data_id=i.data_id", nativeQuery = true, resultSetMapping = "InfoWithDataMap") List<Info> getInfo();
注意原生SQL中需要显式写出两个实体的所有字段,若存在同名字段需要设置别名,避免映射冲突。
优化建议
如果业务场景不是每次查询Info都需要用到关联的Data属性,可以将@OneToOne的加载策略改为懒加载,同时排除@Data生成方法中的关联属性,避免非必要的关联查询:
@Data @ToString(exclude = "data") @EqualsAndHashCode(exclude = "data") @Entity @Table(name = "Info") public class Info { @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "data_id") private Data data; }
内容的提问来源于stack exchange,提问作者Ilia
相关产品推荐
相关产品推荐

