最近在使用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

[2] spring中的懒加载与事务–排坑记录

[3] hibernate join fetch

原文地址:https://blog.csdn.net/johnf_nash/article/details/80658626

Spring Boot JPA 懒加载的更多相关文章

  1. 「新特性」Spring Boot 全局懒加载机制了解一下

    关于延迟加载 在 Spring 中,默认情况下所有定的 bean 及其依赖项目都是在应用启动时创建容器上下文是被初始化的.测试代码如下: @Slf4j @Configuration public cl ...

  2. SpringBoot JPA懒加载异常 - com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy

    问题与分析 某日忽然发现在用postman测试数据时报错如下: com.fasterxml.jackson.databind.JsonMappingException: could not initi ...

  3. Spring boot 国际化自动加载资源文件问题

    Spring boot 国际化自动加载资源文件问题 最近在做基于Spring boot配置的项目.中间遇到一个国际化资源加载的问题,正常来说只要在application.properties文件中定义 ...

  4. Spring Boot的属性加载顺序

        伴随着团队的不断壮大,往往不需要开发人员知道测试或者生产环境的全部配置细节,比如数据库密码,帐号信息等.而是希望由运维或者指定的人员去维护配置信息,那么如果要修改某项配置信息,就不得不去修改项 ...

  5. jpa懒加载异常

    1.项目背景概述 事情是这样子的,使用了spring data jpa的项目jeesite jeesite的实体中使用了懒加载模式. 并且一个实体类中还不止一个属性设置了懒加载模式. 项目本身已经存在 ...

  6. 解决JPA懒加载典型的N+1问题-注解@NamedEntityGraph

    因为在设计一个树形结构的实体中用到了多对一,一对多的映射关系,在加载其关联对象的时候,为了性能考虑,很自然的想到了懒加载. 也由此遇到了N+1的典型问题 : 通常1的这方,通过1条SQL查找得到1个对 ...

  7. Hibernate和Spring整合出现懒加载异常:org.hibernate.LazyInitializationException: could not initialize proxy - no Session

    出现问题:  SSH整合项目里,项目目录结构如下: 在EmployeeAction.java的list()方法里将employees的list放入到request的Map中. EmployeeActi ...

  8. 在IDEA下使用Spring Boot的热加载(Hotswap)

    你是否遇到过这样的困扰: 当你写完一段代码后,要看到效果,必须点击IDEA的停止按钮,然后再次重启启动项目,你是否觉得这样很烦呢? 如果你觉得很烦,本文就是用来解决你的问题的. 所谓热加载,就是让我们 ...

  9. Spring Boot JDBC:加载DataSource过程的源码分析及yml中DataSource的配置

    装载至:https://www.cnblogs.com/storml/p/8611388.html Spring Boot实现了自动加载DataSource及相关配置.当然,使用时加上@EnableA ...

随机推荐

  1. vue制作幻灯片-左右移动

    组件中: <template> <div class="slide-show" @mouseover="clearInv" @mouseout ...

  2. 软工作业———Alpha版本第二周小结

    姓名 学号 周前计划安排 每周实际工作记录 自我打分 zxl 061425 1.进行任务分配2.实现扫码和生成二维码功能 1.对主要任务进行了划分,但还为进行给模块间的联系2.完成了扫码签到功能 90 ...

  3. SQLServer —— 视图

    一.视图的概念 是存储在服务器端的一个查询块,是一张虚拟表. 表示一张表的部分数据或多张表的综合数据. 其结构和数据是建立在对表的查询基础上. 视图的使用,跟对普通的表的查询使用完全一样. 二.视图中 ...

  4. 2019.10.17beta

    import socket import subprocess import os server = socket.socket() server.bind( ('127.0.0.1',8888) ) ...

  5. maven的配置和使用

    Maven 简介 1.1 Maven 是什么 翻译为“专家”,“内行” Maven是跨平台的项目管理工具.主要服务于基于Java平台的项目构建,依赖管理和项目信息管理. 1.2 为什么使用Maven ...

  6. js图片裁切

    js的图片裁切只支持移动和右下拉 html部分 <div id="box" class="box"> <img class="img ...

  7. oracle 共享服务器监控

    1.   观察sga的使用情况 select * from v$sgastat where pool=’large pool’; 2.   观察调度程序是否充足: 首先看每个调度程序的忙闲: sele ...

  8. @codeforces - 1276F@ Asterisk Substrings

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一个包含 n 个小写字母的字符串 s,用 s 生成 n 个串 ...

  9. Inno Setup生成桌面快捷方式

    在做项目的时候,需要打包成exe安装包.先前使用的是vs来打包,生成了setup.exe 和 *.msi的安装文件,不过也算顺利. 后因为要求采取 Inno Setup来打包程序,其中遇到个创建快捷方 ...

  10. 常用的Markdown编辑器, markdown导出HTML/PDF/JSON/word

              markdown导出word.             常用的Markdown 编辑器 OSX Atom,setting-->package install,搜package ...