解决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参数化4-数据库数据引入
1. 下载jmeter依赖的mysql驱动包 jmeter要连接mysql数据库,首先得下载mysql驱动包.执行"select version()"查看数据库版本 mysql历史 ...
- web3 产品介绍 MyEtherWallet 方便和智能合约交互的钱包
MyEtherWallet(简称MEW)是一款流行的去中心化以太坊钱包,它允许用户在安全且简单的界面中管理自己的以太坊资产.在本文中,我们将介绍MyEtherWallet的主要特点.功能以及如何使用它 ...
- LogCat连接安卓手机拉取日志到本地(Unity开发版)
unity开发游戏的时候经常会碰到安卓手机真机报错/崩溃,定位问题需要拉取安卓手机上的日志到电脑上来查看. 1. unity安装的时候,勾选安卓模块(sdk这些记得勾选安装) 2. 打开对应安卓模块个 ...
- 论文写作:写回复意见时的套话 (reviews and comments)
Typos, Grammar, and Citation Errors: We acknowledge the need for a thorough proofreading to rectify ...
- gym中所有可以用的模拟环境
python 代码: from gym import envs for env in envs.registry.all(): print(env.id) 打印出可用环境: Copy-v0 Repea ...
- 有没有使用过MindSpore的,体验怎么样啊?
看到了一个帖子: https://www.zhihu.com/question/386352303/answer/3160948468 ================================ ...
- vscode 设置窗口菜单栏显示字体大小
最近换了一块大些的显示屏,发现vscode的窗口字体有些小了,不是很方便,于是研究了一下如何设置vscode的窗口字体大小. 需要注意的是这里的设置是对窗口字体的而不是编辑器的字体. 1 . 通过主 ...
- SeaTunnel DB2 Source Connector 使用文档(含详细操作步骤)
DB2是IBM的一款关系型数据库管理系统,JDBC DB2 Source Connector是一个用于通过JDBC读取外部数据源数据的连接器.Apache SeaTunnel如何支持JDBC DB2 ...
- 新版的Django Docker部署方案,多阶段构建、自动处理前端依赖
前言 前几天的文章中,我们已经把使用 pdm 的项目用 docker 搞定了,那么下一步就是把完整的 DjangoStarter v3 版本用 docker 部署. 现在不像之前那么简单直接一把梭了, ...
- AtCoder Beginner Contest 318
AtCoder Beginner Contest 318 A - Full Moon (atcoder.jp) 以\(M\)为首项,\(P\)为公差,看\(1 \sim N\)里包含了多少项的个数 # ...