解决org.hibernate.LazyInitializationException的正确姿势

项目运行过程中,一个报错信息,报错信息如下:

org.hibernate.LazyInitializationException: could not initialize proxy [xxx.domain.Guild#CF12263C600F4BCABC9293D3FABE4B42] - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:169) ~[hibernate-core-5.3.9.Final.jar!/:5.3.9.Final]
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309) ~[hibernate-core-5.3.9.Final.jar!/:5.3.9.Final]
    at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45) ~[hibernate-core-5.3.9.Final.jar!/:5.3.9.Final]
    at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95) ~[hibernate-core-5.3.9.Final.jar!/:5.3.9.Final]
    at xxx.domain.Guild$HibernateProxy$58NSae2j.getName(Unknown Source) ~[classes!/:0.0.9-SNAPSHOT]
    at xxx.task.TaskJiaoFuService.guildName(TaskJiaoFuService.java:181) ~[classes!/:0.0.9-SNAPSHOT]
    at xxx.task.TaskJiaoFuService.result2JiaoFuDetail(TaskJiaoFuService.java:122) ~[classes!/:0.0.9-SNAPSHOT]
    at xxx.task.TaskJiaoFuService.parseResult(TaskJiaoFuService.java:106) ~[classes!/:0.0.9-SNAPSHOT]
    at xxx.task.TaskJiaoFuService.queryV4(TaskJiaoFuService.java:91) ~[classes!/:0.0.9-SNAPSHOT]
    at xxx.AbstractExportStrategy.query(AbstractExportStrategy.java:65) ~[classes!/:0.0.9-SNAPSHOT]
    at xxx.ExportService.exportAndPersistence(ExportService.java:130) [classes!/:0.0.9-SNAPSHOT]
    at xxx.ExportService.lambda$execute$0(ExportService.java:75) [classes!/:0.0.9-SNAPSHOT]
    at xxx.common.initialization.ContextCopyingTaskDecorator.lambda$decorate$0(ContextCopyingTaskDecorator.java:20) ~[classes!/:0.0.9-SNAPSHOT]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_181]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_181]
    at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_181]

业务很简单,一个jpa的单表查询,获取属性的时候报错了

分析

JPA默认使用的懒加载,即使访问的是单个实体类,返回的对象也是代理,在获取对象属性的时候才会进行数据库查询,此时如果连接数据session已释放则会抛出上述异常
org.hibernate.LazyInitializationException在经常使用hibernate或者jpa的同学中可能经常遇到,网络上一搜,解决问题的方式有很多种,这里罗列一下:

  • 在spring boot的配置文件application.properties添加spring.jpa.open-in-view=true
  • 用spring 的OpenSessionInViewFilter
  • 在spring boot的配置文件application.properties添加spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
  • 在出问题的实体类上加@Proxy(lazy = false)
  • ……

spring.jpa.open-in-view

我们看下baeldung上是怎么说的,传送门:https://www.baeldung.com/spring-open-session-in-view

Session per request is a transactional pattern to tie the persistence session and request life-cycles together. Not surprisingly, Spring comes with its own implementation of this pattern, named OpenSessionInViewInterceptor, to facilitate working with lazy associations and therefore, improving developer productivity.

………

By default, OSIV is active in Spring Boot applications. Despite that, as of Spring Boot 2.0, it warns us of the fact that it’s enabled at application startup if we haven’t configured it explicitly:

spring.jpa.open-in-view is enabled by default. Therefore, database
queries may be performed during view rendering.Explicitly configure
spring.jpa.open-in-view to disable this warning

意思大致是,每个请求会话对于Spring来说都是一种事务模式,所以了我们默认给你开启了,用于提高开发效率,不过你如果没有显式配置的话,我还会给你一个warning告警。关于这个默认配置在github上争论也有不少:https://github.com/spring-projects/spring-boot/issues/7107
OSIV时序图如下:

解决org.hibernate.LazyInitializationException的正确姿势

项目中spring.jpa.open-in-view是设置为false的,代码获取实体类或者关联实体都是service中完成的,一般我们开启事务,在事务作用的上下文环境中去获取懒加载的数据是不会有任何问题的,且开启之后数据的session会等到整个request完成之后才会释放,其实是十分消耗性能的,之前有其他同学没有关闭open-in-view遇到的问题:https://www.cnblogs.com/thisismarc/p/13594399.html

结论:spring.jpa.open-in-view为true可以解决报错,不过不推荐,OpenSessionInViewFilter配置方案也PASS

spring.jpa.properties.hibernate.enable_lazy_load_no_trans

我们也看下baeldung上是怎么说的,传送门:https://www.baeldung.com/hibernate-lazy-loading-workaround
While using lazy loading in Hibernate, we might face exceptions, saying there is no session.

……

The recommended approach is to design our application to ensure that data retrieval happens in a single transaction. But, this can sometimes be difficult when using a lazy entity in another part of the code that is unable to determine what has or hasn’t been loaded.

Hibernate has a workaround, an enable_lazy_load_no_trans property. Turning this on means that each fetch of a lazy entity will open a temporary session and run inside a separate transaction.

意思大致是,这是一种变通的做法,可以为每个懒加载的实体打开一个临时的会话,不过这个方法也是反人类的,因为如果延迟加载的关联实体越多,请求附加的连接也就越多,这会给数据库连接带来压力。在新事务中加载的每个关联,在每次关联初始化后都会强制刷新事务日志,所以大大的不建议使用。

@Proxy(lazy = false)

@Proxy(lazy = false)的意思和FetchType.EAGER类似,返回的是初始化好的实体,即关闭了懒加载,这个肯定不是我们想要的

推荐解决方式

回到我们的问题,单表懒加载报错,项目使用的是Springboot,事务都是显式的注解配置,查询的接口我们一般没有配置@Transactional注解,所以解决方法是在Service的查询方法上增加 @Transactional(readOnly = true) 注解来划分事务边界,这是比较推荐的做法,也复合编码规范,项目中如果有事务切面配置,把相关方法加到事务控制的范围中则也不会出现这个问题,如果还有其他更好的方式,欢迎留言

参考链接

https://vladmihalcea.com/the-hibernate-enable_lazy_load_no_trans-anti-pattern/
https://www.baeldung.com/spring-open-session-in-view
https://github.com/spring-projects/spring-boot/issues/7107
https://www.cnblogs.com/thisismarc/p/13594399.html
https://www.baeldung.com/hibernate-lazy-loading-workaround

Original: https://www.cnblogs.com/surging-dandelion/p/15085605.html
Author: 蒲公英的狂想
Title: 解决org.hibernate.LazyInitializationException的正确姿势

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/579584/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 突破

    象棋的目标是赢棋,而不是谋子。突破了「谋子」这一认知的棋手,招式中就多了欲擒故纵。甚至棋手心里都没想着招式,只是在朝着目标布局。 象棋的目标是赢棋,而不是谋子。突破了「谋子」这一认…

    Java 2023年6月16日
    090
  • 通过调试来理解形参与实参的区别

    刚开始学习模块化程序设计时,估计大家都被形参和实参搞迷糊过,尤其是遇到形参名和实参名一样时,更加晕头转向,出现一种”是谁把值传给了我,而我又传给了谁”的疑惑…

    Java 2023年6月8日
    085
  • spring boot html+vue.js 形式前后分离代码示例

    1.html <table <span class="hljs-class"><span class="hljs-keywor…

    Java 2023年6月8日
    068
  • 【一知半解】零值拷贝

    传统IO 应用调用read方法向操作系统发起读数据的请求,此时由 用户态切换为 内核态 当系统收到读数据请求时,利用DMA控制器把数据从磁盘读取到系统缓存区中(图中2.1) 再然后…

    Java 2023年6月9日
    086
  • 5个步骤,教你瞬间明白线程和线程安全

    记得刚来杭州面试的时候,有一家公司的技术总监问了我这样一个问题:你来说说有哪些线程安全的类?我心里一想,这我早都背好了,稀里哗啦说了一大堆。 他又接着问:那你再来说说什么是线程安全…

    Java 2023年6月5日
    056
  • 十六、多线程(基础)(完结)

    十六、多线程(基础) 16.1 线程相关概念 16.1.1 程序 是为完成特定任务、用某种语言编写的一组指令的集合。 简单的说:就是我们写的代码 16.1.2 进程 进程是指运行中…

    Java 2023年6月5日
    0141
  • 创建并操作循环链表

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月5日
    073
  • 手把手教你使用 Spring Boot 3 开发上线一个前后端分离的生产级系统(八)-XXL-JOB 集成与配置

    手把手教你使用 Spring Boot 3 开发上线一个前后端分离的生产级系统(一) – 介绍手把手教你使用 Spring Boot 3 开发上线一个前后端分离的生产级…

    Java 2023年6月8日
    089
  • 【源码笔记】构建Spring源码环境

    IDEA构建Spring源码,不成功你来抓我 posted @2022-07-14 22:43 daheww 阅读(15 ) 评论() 编辑 Original: https://w…

    Java 2023年6月6日
    086
  • java基础4.19

    1.JAVA 的反射机制的原理。JAVA反射机制是在运行状态中,对于 任意一个类,都能够 知道这个类的 所有属性和方法;对于任意一个对象,都能够调 用它的任意一个方法;这种 动态获…

    Java 2023年6月5日
    072
  • Java(11)自定义类

    之前的例子中,我们已经编写了一些简单的类。但是,那些类都只包含一个简单的main方法。现在来学习如何编写复杂应用程序所需要的那种主力类。通常这些类没有main方法,却有自己的实例字…

    Java 2023年6月9日
    084
  • 面试突击71:GET 和 POST 有什么区别?

    GET 和 POST 是 HTTP 请求中最常用的两种请求方法,在日常开发的 RESTful 接口中,都能看到它们的身影。而它们之间的区别,也是一道常见且经典的面试题,所以我们本文…

    Java 2023年5月29日
    068
  • Nginx代理websocket配置(解决websocket异常断开连接tcp连接不断问题)

    场景 SpringBoot+Vue整合WebSocket实现前后端消息推送: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/d…

    Java 2023年5月30日
    096
  • 分库分表后的索引问题

    最近遇到一个慢sql,在排查过程中发现和分库分表后的索引设置有关系,总结了下问题。 在进行应用健康度盘点时,发现有个慢sql如下 select brandgoodid from b…

    Java 2023年6月8日
    0142
  • catch中return语句的执行时间

    catch中return语句的执行时间 先考虑以下一个普通的示例: public static int main() { System.out.println("没有参数…

    Java 2023年6月7日
    066
  • Vmware 虚拟机无法启动

    问题背景: 自己的电脑坏了,用的事小伙伴的电脑,安装VMware 软件,然后创建虚拟机(放在移动硬盘上)。在操作虚拟主机的时候,中间不小心碰到了移动硬盘, 然后移动硬盘就掉线了。这…

    Java 2023年5月30日
    069
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球