Spring Boot JPA 懒加载
最近在使用spring jpa 的过程中经常遇到懒加载的错误:“`
org.hibernate.LazyInitializationException: could not initialize proxy [xxxx#18] - no Session
通过查询资料,整理了一下常见的几种解决办法。
一、spring.jpa.open-in-view 配置
测试 dao 层或者 service 层时,会出现 no Session 的错误;访问 controller 时,又不会出现上面的错误。查询资料发现,spring boot web 会引入一个一个配置
spring.jpa.open-in-view=true
这个配置的说明如下:
spring.jpa.open-in-view
java.lang.Boolean
Default: true
Register OpenEntityManagerInViewInterceptor.
Binds a JPA EntityManager to the thread for the entire processing
of the request.
该配置会注册一个OpenEntityManagerInViewInterceptor。在处理请求时,将 EntityManager 绑定到整个处理流程中(model->dao->service->controller),开启和关闭session。这样一来,就不会出现 no Session 的错误了(可以尝试将该配置的值置为 false, 就会出现懒加载的错误了。)
二、非 web 请求下的懒加载问题解决
最近遇到一个quartz定时任务处理的,不需要通过 web 请求,就可以直接访问数据库。这种情况下,spring.jpa.open-in-view 这个配置就不起作用了,需要通过其它的方式处理懒加载的问题。
下面介绍其中两种方式。
1. spring.jpa.properties.hibernate.enable_lazy_load_no_trans 配置
这个配置是 hibernate 中的(其它 JPA Provider 中无法使用),当配置的值是 true 的时候,允许在没有 transaction 的情况下支持懒加载。
下面通过一个用户与权限的多对多的关联的例子来说明。
用户实体类
package com.johnfnash.learn.domain;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 20, unique = true)
private String username; // 用户账号,用户登录时的唯一标识
@Column(length = 100)
private String password; // 登录时密码
@ManyToMany
@JoinTable(name = "user_authority", joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "authority_id"))
//1、关系维护端,负责多对多关系的绑定和解除
//2、@JoinTable注解的name属性指定关联表的名字,joinColumns指定外键的名字,关联到关系维护端(User)
//3、inverseJoinColumns指定外键的名字,要关联的关系被维护端(Authority)
//4、其实可以不使用@JoinTable注解,默认生成的关联表名称为主表表名+下划线+从表表名,
//即表名为user_authority
//关联到主表的外键名:主表名+下划线+主表中的主键列名,即user_id
//关联到从表的外键名:主表中用于关联的属性名+下划线+从表的主键列名,即authority_id
//主表就是关系维护端对应的表,从表就是关系被维护端对应的表
private List<Authority> authorityList;
public User() {
super();
}
public User(String username, String password, List<Authority> authorityList) {
super();
this.username = username;
this.password = password;
this.authorityList = authorityList;
}
// getter, setter
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + "]";
}
}
注:User 实体类作为多读多关系维护端,里维护了相关的 权限列表。
权限实体类
package com.johnfnash.learn.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(nullable = false)
private String name; //权限名
public Authority() {
super();
}
public Authority(String name) {
super();
this.name = name;
}
// getter, setter
@Override
public String toString() {
return "Authority [id=" + id + ", name=" + name + "]";
}
}
UserRepository.java
package com.johnfnash.learn.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.johnfnash.learn.domain.User;
public interface UserRepository extends JpaRepository<User, Long> {
}
AuthorityRepository.java
package com.johnfnash.learn.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.johnfnash.learn.domain.Authority;
public interface AuthorityRepository extends JpaRepository<Authority, Integer> {
}
测试
package com.johnfnash.learn;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.johnfnash.learn.domain.Authority;
import com.johnfnash.learn.domain.User;
import com.johnfnash.learn.repository.AuthorityRepository;
import com.johnfnash.learn.repository.UserRepository;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Autowired
private AuthorityRepository authorityRepository;
@Test
public void saveUser() {
Authority authority = new Authority("ROLE_ADMIN");
authorityRepository.save(authority);
User user = new User();
user.setUsername("admin");
user.setPassword("123456");
List<Authority> authorityList = new ArrayList<Authority>();
authorityList.add(authority);
user.setAuthorityList(authorityList);
userRepository.save(user);
}
@Test
public void queryUser() {
User user = userRepository.getOne(1L);
System.out.println(user);
//System.out.println(user.getAuthorityList());
}
}
调用 saveUser 方法插入测试数据后,再执行 queryUser 查询用户数据,报 No Session 的错误。application.properties 中添加如下配置:
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
再执行 queryUser 方法,查询成功,sql如下:
Hibernate: select user0_.id as id1_5_0_, user0_.password as password2_5_0_,
user0_.username as username3_5_0_ from user user0_ where user0_.id=?
这个时候由于只访问了 user 的基本信息,所以没有查询 authority 表。
打开 queryUser 方法里的注释,再执行 queryUser 方法,会执行如下两条sql:
Hibernate: select user0_.id as id1_5_0_, user0_.password as password2_5_0_,
user0_.username as username3_5_0_ from user user0_ where user0_.id=?
Hibernate: select authorityl0_.user_id as user_id1_6_0_,
authorityl0_.authority_id as authorit2_6_0_,
authority1_.id as id1_3_1_, authority1_.name as name2_3_1_
from user_authority authorityl0_
inner join authority authority1_ on authorityl0_.authority_id=authority1_.id
where authorityl0_.user_id=?
通过上面的例子,我们可以看到添加这个配置之后,确实实现了懒加载。
不过这种方式会产生 N+1 的影响,上面的例子这个一个用户有多个权限,可能会进行 1 + N 次查询。如果这时 Authority 又与多个 Role 关联,使用不当的话,查询次数可能就变成了 1 + N * M 。
2. 通过在查询中使用 fetch 的方式
通过再查询中使用 fetch,一次将相关数据查询出来,不会产生 N + 1 的影响。
继续使用上面的 用户-权限 的例子。先把 spring.jpa.properties.hibernate.enable_lazy_load_no_trans 这个配置去掉,然后在UserRepository 中添加如下方法:
@Query("from User u join fetch u.authorityList")
public User findOne(Long id);
测试类中的 queryUser 代码改为下面的:
@Test
public void queryUser() {
User user = userRepository.findOne(2L);
System.out.println(user);
System.out.println(user.getAuthorityList());
}
进行查询,只会执行一条sql:
Hibernate: select user0_.id as id1_5_0_, authority2_.id as id1_3_1_,
user0_.password as password2_5_0_, user0_.username as username3_5_0_,
authority2_.name as name2_3_1_, authorityl1_.user_id as user_id1_6_0__,
authorityl1_.authority_id as authorit2_6_0__
from user user0_
inner join user_authority authorityl1_ on user0_.id=authorityl1_.user_id
inner join authority authority2_ on authorityl1_.authority_id=authority2_.id
通过 sql 可以看出,实际上就是使用了sql 里的 join 一次查询出来多条数据。
参考
[1] Solve Hibernate Lazy-Init issue with hibernate.enable_lazy_load_no_trans
原文地址:https://blog.csdn.net/johnf_nash/article/details/80658626
Spring Boot JPA 懒加载的更多相关文章
- 「新特性」Spring Boot 全局懒加载机制了解一下
关于延迟加载 在 Spring 中,默认情况下所有定的 bean 及其依赖项目都是在应用启动时创建容器上下文是被初始化的.测试代码如下: @Slf4j @Configuration public cl ...
- SpringBoot JPA懒加载异常 - com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy
问题与分析 某日忽然发现在用postman测试数据时报错如下: com.fasterxml.jackson.databind.JsonMappingException: could not initi ...
- Spring boot 国际化自动加载资源文件问题
Spring boot 国际化自动加载资源文件问题 最近在做基于Spring boot配置的项目.中间遇到一个国际化资源加载的问题,正常来说只要在application.properties文件中定义 ...
- Spring Boot的属性加载顺序
伴随着团队的不断壮大,往往不需要开发人员知道测试或者生产环境的全部配置细节,比如数据库密码,帐号信息等.而是希望由运维或者指定的人员去维护配置信息,那么如果要修改某项配置信息,就不得不去修改项 ...
- jpa懒加载异常
1.项目背景概述 事情是这样子的,使用了spring data jpa的项目jeesite jeesite的实体中使用了懒加载模式. 并且一个实体类中还不止一个属性设置了懒加载模式. 项目本身已经存在 ...
- 解决JPA懒加载典型的N+1问题-注解@NamedEntityGraph
因为在设计一个树形结构的实体中用到了多对一,一对多的映射关系,在加载其关联对象的时候,为了性能考虑,很自然的想到了懒加载. 也由此遇到了N+1的典型问题 : 通常1的这方,通过1条SQL查找得到1个对 ...
- Hibernate和Spring整合出现懒加载异常:org.hibernate.LazyInitializationException: could not initialize proxy - no Session
出现问题: SSH整合项目里,项目目录结构如下: 在EmployeeAction.java的list()方法里将employees的list放入到request的Map中. EmployeeActi ...
- 在IDEA下使用Spring Boot的热加载(Hotswap)
你是否遇到过这样的困扰: 当你写完一段代码后,要看到效果,必须点击IDEA的停止按钮,然后再次重启启动项目,你是否觉得这样很烦呢? 如果你觉得很烦,本文就是用来解决你的问题的. 所谓热加载,就是让我们 ...
- Spring Boot JDBC:加载DataSource过程的源码分析及yml中DataSource的配置
装载至:https://www.cnblogs.com/storml/p/8611388.html Spring Boot实现了自动加载DataSource及相关配置.当然,使用时加上@EnableA ...
随机推荐
- CEF 框架使用集锦
CEF 框架使用集锦: 参考:〓https://github.com/NetDimension/NanUI/wiki/%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8NanUI ...
- 制作ACK集群自定义节点镜像的正确姿势
随着云原生时代的到来,用户应用.业务上云的需求也越来越多,不同的业务场景对容器平台的需求也不尽相同,其中一个非常重要的需求就是使用自定义镜像创建ACK集群. ACK支持用户使用自定义镜像创建Kuber ...
- qt加载背景图片的一种方式
//加载背景图片 void LCTGrid::loadBgPicture() { QImage image; QPalette palette; image.load(m_sPicturePath); ...
- 【OI】拓扑排序
拓扑排序 首先要求图为DAG 算法:首先将度为1的节点加入队列每次取出队首点u,在图中删去和u相邻的边继续将度数为1的点加入队列 到了最后, 如果没有度数为1的点,则图不是DAG 通过拓扑排序可以给D ...
- day39 07-Spring的AOP:自动代理
带有切点的切面或者是不带有切点的切面配置一个类就要配置一段生成代理的代码,这样太麻烦了. 选中orderDao右键watch JDK动态代理是先创建被代理对象,然后在创建代理对象的时候传入被代理对象. ...
- pl/sql基础知识—过程快速入门
n 过程 过程用于执行特定的操作,当建立过程时,既可以指定输入参数(in),也可以指定输出参数(out),通过在过程中使用输入参数,可以将数据传递到执行部分:通过使用输出参数可以将执行部分的数据传递 ...
- js单选按钮的默认值
function SelectWindow(str) { initradio('PhysiotherapyOptionsTable.Sex',sex); } function initra ...
- spark编译与onyarn的执行
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/u014393917/article/details/24640715 Spark on yarn执行 ...
- vue vscode属性标签不换行
"vetur.format.defaultFormatterOptions": { "js-beautify-html": { "wrap_attri ...
- 巨蟒python全栈开发-第11阶段 ansible_project1
今日大纲: 1.前端页面介绍 2.发布流程 3.需求分析 4.表结构设计 5.前端页面设计 昨日内容回顾: 1.roles - tasks - handlers - files - templates ...