解决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: 这句话的意思是:找不到此列 为什么会出 ...
随机推荐
- Jmeter函数助手28-urldecode
urldecode函数用于解码application/x-www-form-urlencoded字符串. String to encode in URL encoded chars:填入applica ...
- a-from提交时遇到errorFields:[]验证错误(vue3)
应用场景:使用a-form组件,里面使用a-select组件:当a-select组件内的值发生改变时,调用a-form的验证表单,进而提交. 问题:提交时遇到errorFields:[]验证错误 解决 ...
- 6、SpringBoot2之整合Mybatis
创建名为springboot_mybatis的新module,过程参考3.1节 6.1.引入相关依赖 注意:虽然本文使用的是 spring boot 2.7.18 和 MySQL 5.7 ,但是出于可 ...
- 【微信小程序】 分包
1. 什么是分包 分包指的是把一-个完整的小程序项目,按照需求划分为不同的子包, 在构建时打包成不同的分包,用户在使用时按需进行加载. 2. 分包的好处 对小程序进行分包的好处主要有以下两点: ● 可 ...
- 【Mybatis-Plus】01 快速上手
[官网快速上手地址] https://mp.baomidou.com/guide/quick-start.html#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8 ...
- python报错:Pip 20.3+ break proxy connection
参考: https://www.cnblogs.com/devilmaycry812839668/p/17872452.html =================================== ...
- 【转载】How to Use t-SNE Effectively —— (机器学习数据可视化) t-SNE使用指南
原文地址:https://distill.pub/2016/misread-tsne/ 说明: 原文是比较有名的一个指南性博文,讲的就是t-SNE技术的一些使用注意事项和说明,属于说明性文章,内容很不 ...
- WSL启动nvidia-docker镜像:报错libnvidia-ml.so.1- file exists- unknown
参考: https://blog.csdn.net/xujiamin0022016/article/details/124782913 https://zhuanlan.zhihu.com/p/361 ...
- baselines算法库run.py模块分析
baselines算法库地址: https://gitee.com/devilmaycry812839668/baselines =================================== ...
- 除了Ubuntu以外的Linux系统可以安装Tensorflow/Pytorch的GPU版本吗???
废话: 平时没事有用到的Linux系统有Centos/Redhat/Ubuntu/UOS/Deepin,其中Ubuntu系统主要是用来工作生产的,UOS就是看看国家队的进展如何,Deepin就是看看民 ...