解决Spring Data JPA Hibernate的N+1查询问题的性能优化最佳方法
最佳方法:定制@NamedEntityGraph、定制查询和定制VO,可以做到按照需要最佳查询,需要注意的地方:定制VO的字段一定要等于或小于实际查询的字段,才不会复制的时候触发N+1查询。
1 问题复现
1.1 项目结构

1.2 entity
package com.xkzhangsan.jpa.entity; import lombok.Getter;
import lombok.Setter; import javax.persistence.*; @Entity
@Getter
@Setter
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; private String name; @OneToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
@JoinColumn(name = "user_detail_id")
private UserDetail userDetail;
}
package com.xkzhangsan.jpa.entity; import lombok.Getter;
import lombok.Setter; import javax.persistence.*; @Entity
@Getter
@Setter
@Table(name = "user_detail")
public class UserDetail {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; private String address;
}
1.3 repository
package com.xkzhangsan.jpa.repository; import com.xkzhangsan.jpa.entity.User;
import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Integer> {
}
1.4 service
package com.xkzhangsan.jpa.service; import com.xkzhangsan.jpa.entity.User;
import com.xkzhangsan.jpa.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import java.util.List; @Service
public class UserService {
@Autowired
private UserRepository userRepository; public List<User> findAll(){
return userRepository.findAll();
} }
1.5 controller
package com.xkzhangsan.jpa.controller; import com.xkzhangsan.jpa.entity.User;
import com.xkzhangsan.jpa.service.UserService;
import com.xkzhangsan.jpa.vo.UserDetailVO;
import com.xkzhangsan.jpa.vo.UserVO;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import java.util.List;
import java.util.stream.Collectors; @RestController
public class UserController { @Autowired
private UserService userService; @RequestMapping(value = "/")
public List<UserVO> getPersons() {
ModelMapper modelMapper = new ModelMapper();
List<User> userList = userService.findAll();
if (!CollectionUtils.isEmpty(userList)) {
return userList.stream().map(user -> {
UserVO userVO = modelMapper.map(user, UserVO.class);
UserDetailVO userDetailVO = modelMapper.map(user.getUserDetail(), UserDetailVO.class);
userVO.setUserDetailVO(userDetailVO);
return userVO;
}).collect(Collectors.toList());
}
return null;
}
}
1.6 测试


查询1次,实际查询了4次,这里的N指的是集合的数量3,所以 N+1,就是3+1=4
1.7 问题原因
User关联对象懒加载导致

2 最佳解决方法
定制@NamedEntityGraph、定制查询和定制VO,可以做到按照需要最佳查询。
2.1 定制@NamedEntityGraph
package com.xkzhangsan.jpa.entity; import lombok.Getter;
import lombok.Setter; import javax.persistence.*; @Entity
@Getter
@Setter
@Table(name = "user")
@NamedEntityGraph(name = "user.userDetail", attributeNodes = {
@NamedAttributeNode(value = "userDetail")
})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; private String name; @OneToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
@JoinColumn(name = "user_detail_id")
private UserDetail userDetail;
}
从代码种可以看出增加注解,使用user.userDetail会联表查询UserDetail
@NamedEntityGraph(name = "user.userDetail", attributeNodes = {
@NamedAttributeNode(value = "userDetail")
})
2.1.2 定制查询
package com.xkzhangsan.jpa.repository; import com.xkzhangsan.jpa.entity.User;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface UserRepository extends JpaRepository<User, Integer> { @Override
@EntityGraph(value = "user.userDetail", type = EntityGraph.EntityGraphType.FETCH)
List<User> findAll();
}
从代码种可以看出,重写了findAll,使用了user.userDetail
2.3 定制VO(可选)
根据需要返回的字段定义一个VO,如果没有需要则不需要定制,比如当前实例种就不需要。
测试结果如下,只查询了一次,但联表查询了,left outer join user_detail
select user0_.id as id1_0_0_, userdetail1_.id as id1_1_1_, user0_.name as name2_0_0_, user0_.user_detail_id as user_det3_0_0_, userdetail1_.address as address2_1_1_ from user user0_ left outer join user_detail userdetail1_ on user0_.user_detail_id=userdetail1_.id
2.3.1 需要定制VO的实例
比如,只想返回用户id和name,可以定制一个UserSimpleVO,如下:
package com.xkzhangsan.jpa.vo; import lombok.Getter;
import lombok.Setter; @Getter
@Setter
public class UserSimpleVO {
private Long id; private String name;
}
因为不包括UserDetailVO userDetailVO,所以复制属性的时候不会触发更多的查询。
需要注意的地方:定制VO的字段一定要等于或小于实际查询的字段,才不会复制的时候触发N+1查询。
还有一种方式不用定制VO,通过设置ModelMapper跳过不需要的字段,但这样有2个问题
(1)需要设置ModelMapper,比较麻烦
(2)既然不需要一些字段,定制VO是最有效的方法,这样符合迪米特法则,比如不能展示的敏感字段,如果查询或处理过程中没有处理好,可能导致误返回,最好定制VO,在VO删除这个字段。
3 使用EAGER加载方式
既然是因为LAZY导致的,改成EAGER是否可以解决问题?经过验证是不可行的,仍然会查询多次,比如:
package com.xkzhangsan.jpa.entity; import lombok.Getter;
import lombok.Setter; import javax.persistence.*; @Entity
@Getter
@Setter
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id; private String name; @OneToOne(cascade = CascadeType.DETACH, fetch = FetchType.EAGER)
@JoinColumn(name = "user_detail_id")
private UserDetail userDetail;
}
发现仍然会执行多个sql
Hibernate: select user0_.id as id1_0_, user0_.name as name2_0_, user0_.user_detail_id as user_det3_0_ from user user0_
Hibernate: select userdetail0_.id as id1_1_0_, userdetail0_.address as address2_1_0_ from user_detail userdetail0_ where userdetail0_.id=?
Hibernate: select userdetail0_.id as id1_1_0_, userdetail0_.address as address2_1_0_ from user_detail userdetail0_ where userdetail0_.id=?
Hibernate: select userdetail0_.id as id1_1_0_, userdetail0_.address as address2_1_0_ from user_detail userdetail0_ where userdetail0_.id=?
解决Spring Data JPA Hibernate的N+1查询问题的性能优化最佳方法的更多相关文章
- SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法
软件152 尹以操 首先谢谢大佬的简书文章:http://www.jianshu.com/p/45ad65690e33# 这篇文章中讲的是spring中使用spring data jpa,使用了xml ...
- 【hql】spring data jpa中 @Query使用hql查询 问题
spring data jpa中 @Query使用hql查询 问题 使用hql查询, 1.from后面跟的是实体类 不是数据表名 2.字段应该用实体类中的字段 而不是数据表中的属性 实体如下 hql使 ...
- Spring Boot 2.x 之 Spring Data JPA, Hibernate 5
1. Spring Boot常用配置项 基于Spring Boot 2.0.6.RELEASE 1.1 配置属性类 spring.jpa前缀的相关配置项定义在JpaProperties类中, 1.2 ...
- spring data jpa hibernate jpa 三者之间的关系
JPA规范与ORM框架之间的关系是怎样的呢? JPA规范本质上就是一种ORM规范,注意不是ORM框架——因为JPA并未提供ORM实现,它只是制订了一些规范,提供了一些编程的API接口,但具体实现则由服 ...
- spring data jpa 使用JPQL的方式查询
用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询 @Que ...
- Spring Data JPA 复杂/多条件组合查询
1: 编写DAO类或接口 dao类/接口 需继承 public interface JpaSpecificationExecutor<T> 接口: 如果需要分页,还可继承 public ...
- Spring Data JPA 实现多表关联查询
本文地址:https://liuyanzhao.com/6978.html 最近抽出时间来做博客,数据库操作使用的是 JPA,相对比 Mybatis 而言,JPA 单表操作非常方便,增删改查都已经写好 ...
- 使用Spring Data JPA的Specification构建数据库查询
Spring Data JPA最为优秀的特性就是可以通过自定义方法名称生成查询来轻松创建查询SQL.Spring Data JPA提供了一个Repository编程模型,最简单的方式就是通过扩展Jpa ...
- Java Data JPA +hibernate 保存或者是查询遇到的坑
由于项目需求,接触了Java Data JPA +hibernate,它的调用方式是controller调用service,service有实现的接口serviceimpl,serviceimpl调用 ...
- Spring data jpa hibernate:查询异常java.sql.SQLException: Column '列名' not found
使用spring boot,jap,hibernate不小心的错误: java.sql.SQLException: Column '列名' not found: 这句话的意思是:找不到此列 为什么会出 ...
随机推荐
- 【TypeScript】Re01
参考视频: https://www.bilibili.com/video/BV14Z4y1u7pi JS已有类型: 基础类型 number / string / boolean / null / un ...
- 【Java】讲讲StreamAPI
预设场景: 从Mybatis调用Mapper得到的用户集合 List<UserDTO> userList = new ArrayList<>(); 常用的几种API用法示例: ...
- 【Hibernate】Re03 注解方式实现
使用JPA规范提供的注解即可实现,这样的好处是不需要配置Entity.hbm.xml文件了 但是考虑到多表查询的情况,还是会有xml配置的需要. 一.常用的JPA注解: 1.public @inter ...
- 人形机器人操作系统(开源) —— FreeRTOS
地址: https://www.freertos.org/zh-cn-cmn-s/index.html
- reinforcement learning常用的游戏环境,gym框架使用的标准Atari游戏集合
reinforcement learning常用的游戏环境,gym框架使用的标准Atari游戏集合.*.bin文件为Atari2600游戏的常用游戏环境的模拟文件,也称为roms文件. 文件地址: h ...
- Committer 迎新!这次是来自阿里云的同学
截至今天,Apache DolphinScheduler 项目在 GitHub 上的 Star 数已突破 10.6K,贡献者人数也突破了 470 人.社区的不断壮大,离不开每位 Contributor ...
- 二分答案&前缀和&差分&离散化(简记)
二分答案 基本code int Find(int l,int r) { int ans,mid; while(l<=r) { int mid=l+r>>1; if(Check(mid ...
- WPF,图表控件
开源代码地址:https://github.com/bearhanQ/WPFFramework; QQ群:332035933: <UserControl x:Class="WpfBoo ...
- win10无法访问共享文件夹win2008R2 错误代码0X80004005
错误代码0X80004005 无法访问共享计算机的解决方法 开始->运行(快捷键"win+R"),输入"regedit"后回车,打开注册表编辑器. 依次打 ...
- 树莓派CM4(四):树莓派镜像替换内核
树莓派镜像替换内核 1. 为什么要替换内核 树莓派官方提供的镜像中,自带的内核版本为6.6.31 然而github上提供的内核源码为6.6.40,有些微差别 此外,后续很有可能进行内核裁剪定制等工作, ...