最佳方法:定制@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查询问题的性能优化最佳方法的更多相关文章

  1. SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法

    软件152 尹以操 首先谢谢大佬的简书文章:http://www.jianshu.com/p/45ad65690e33# 这篇文章中讲的是spring中使用spring data jpa,使用了xml ...

  2. 【hql】spring data jpa中 @Query使用hql查询 问题

    spring data jpa中 @Query使用hql查询 问题 使用hql查询, 1.from后面跟的是实体类 不是数据表名 2.字段应该用实体类中的字段 而不是数据表中的属性 实体如下 hql使 ...

  3. 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 ...

  4. spring data jpa hibernate jpa 三者之间的关系

    JPA规范与ORM框架之间的关系是怎样的呢? JPA规范本质上就是一种ORM规范,注意不是ORM框架——因为JPA并未提供ORM实现,它只是制订了一些规范,提供了一些编程的API接口,但具体实现则由服 ...

  5. spring data jpa 使用JPQL的方式查询

    用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询 @Que ...

  6. Spring Data JPA 复杂/多条件组合查询

    1: 编写DAO类或接口  dao类/接口 需继承 public interface JpaSpecificationExecutor<T> 接口: 如果需要分页,还可继承 public ...

  7. Spring Data JPA 实现多表关联查询

    本文地址:https://liuyanzhao.com/6978.html 最近抽出时间来做博客,数据库操作使用的是 JPA,相对比 Mybatis 而言,JPA 单表操作非常方便,增删改查都已经写好 ...

  8. 使用Spring Data JPA的Specification构建数据库查询

    Spring Data JPA最为优秀的特性就是可以通过自定义方法名称生成查询来轻松创建查询SQL.Spring Data JPA提供了一个Repository编程模型,最简单的方式就是通过扩展Jpa ...

  9. Java Data JPA +hibernate 保存或者是查询遇到的坑

    由于项目需求,接触了Java Data JPA +hibernate,它的调用方式是controller调用service,service有实现的接口serviceimpl,serviceimpl调用 ...

  10. Spring data jpa hibernate:查询异常java.sql.SQLException: Column '列名' not found

    使用spring boot,jap,hibernate不小心的错误: java.sql.SQLException: Column '列名' not found: 这句话的意思是:找不到此列 为什么会出 ...

随机推荐

  1. 【H5】06 网页架构

    摘自: https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML/%E6%96%87%E4%BB%B6%E5%9 ...

  2. 【Windows】解决微软商店打不开的问题

    参考贴吧的帖子: https://tieba.baidu.com/p/6028738660#123983609458l 1.打开"运行"输入 inetcpl.cpl (" ...

  3. 【SpringMVC】05 RestFul风格

    什么是RestFul风格? 一个资源定位和资源操作的风格,不是标准,也不是协议, 基于此风格的路径访问可以隐藏真实的参数传递,以提高网站的安全访问 以往的请求参数: jdbc:mysql://loca ...

  4. 【Redis】02 Redis 搭建与操作

    Redis的安装及启动停止 官网地址: https://redis.io/download 使用wget命令下载redis wget 下载地址 下载: [root@VM-0-7-centos ~]# ...

  5. 关于Vue + element plus包装Component理解

    关于Vue + element plus包装Component理解 一.关于编写思路 我以设计el-select选择框进行举例说明 父组件与Component传递params与Function使用Pr ...

  6. springboot之banner.txt

    在springboot启动过程中,我们经常可以看到控制台打印下面图文: 实际上这个打印图文,是可以自定义的,可以在springboot的resource中创建一个banner.txt文件,在启动时就会 ...

  7. 什么是AOP,以及在Springboot中自定义AOP

    AOP (Aspect Oriented Programming)一般译为面向切面编程 Aspect [ˈæspekt] n.方面;层面;(动词的)体那么AOP 面相切面编程具体是指什么,它和之前的O ...

  8. JavaScript设计模式样例二十一 —— 解释器模式

    解释器模式(Interpreter Pattern) 定义:提供了评估语言的语法或表达式的方式.目的:对于一些固定文法构建一个解释句子的解释器.场景:编译器.运算表达式计算. // 定义对于语法的断言 ...

  9. 倍增优化DP

    对于只考虑首位状态的DP,考虑用倍增优化 P1081 开车旅行 https://www.luogu.org/problemnew/show/P1081 1 const int N=100005; 2 ...

  10. 在 SQLAlchemy 中实现数据处理的时候,实现表自引用、多对多、联合查询,有序id等常见的一些经验总结

    有时候,我们在使用SQLAlchemy操作某些表的时候,需要使用外键关系来实现一对多或者多对多的关系引用,以及对多表的联合查询,有序列的uuid值或者自增id值,字符串的分拆等常见处理操作. 1.在 ...