解决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函数助手18-machineIP
machineIP函数用于获取本地IP地址. 存储结果的变量名(可选) 1.本地ip可以在cmd运行界面输入命令"ipconfig"查看,Jmeter则使用函数获取${__mach ...
- .NET 8 通用权限框架 前后端分离,开箱即用
前言 推荐一个基于.NET 8 实现的通用权限开发框架Admin.NET,前端使用Vue3/Element-plus开发. 基于.NET 8(Furion)/SqlSugar实现的通用管理平台.整合 ...
- 轻松搞定 Nginx 在 CentOS 和 Ubuntu 上的安装与配置
注:这是对我以前博客进行优化后再次发布的,博客中的截图为以前的.原博客已删除. 如何安装nginx nginx是一款开源.高性能的Web和反向代理服务器,支持HTTP.HTTPS.SMTP.POP3和 ...
- 生成式 AI:机会与风险并存,企业该如何取舍?
作者 | 李晨 编辑 | Debra Chen Gartner最近对全球2,500名高管进行的一项调查发现,近一半(45%)的人表示,ChatGPT的宣传促使他们增加人工智能(AI)投资.调查报告称, ...
- VUE——语法糖
- ChatGPT学习之旅 (9) 系统运维小助手
大家好,我是Edison. 上一篇我们写了一个单元测试助手的prompt,它帮我们写一些我们开发者不太愿意编写的单元测试代码,在我最近一个月的实践中我再也没有手写过单元测试,更多地只是在AI生成的代码 ...
- CentOs7.3 配置基本信息查看
1.基本信息查看(命令行) [root@localhost home]# CPU个数: [root@localhost home]# grep 'physical id' /proc/cpuinfo ...
- 推荐7款美观且功能强大的WPF UI库
前言 经常看到有小伙伴在DotNetGuide技术社区交流群里提问:WPF有什么好用或者好看的UI组件库推荐的?,今天大姚给大家分享7款开源.美观.功能强大.简单易用的WPF UI组件库. WPF介绍 ...
- 实用接地气的 .NET 微服务框架
前言 微服务架构已经成为搭建高效.可扩展系统的关键技术之一,然而,现有许多微服务框架往往过于复杂,使得我们普通开发者难以快速上手并体验到微服务带了的便利.为了解决这一问题,于是作者精心打造了一款最接地 ...
- Manual 命令手册导出
man 命令的输出中含有大量的格式化字符和终端控制序列,这些是用来在终端中正确显示格式化文本(如加粗,下划线,分页等)所用的.如 BS(Backspace,退格). 如果我们直接用 man comma ...