解决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)

大家都在看

  • JavaWeb开发的一些问题

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

    Java 2023年5月29日
    063
  • Java面向对象(二)

    Java面向对象(二) Java面向对象(二) – 五、方法 5.1 方法的重载(overload) 5.2 可变参数的形参 5.3 方法参数的值传递机制 5.4 递归…

    Java 2023年6月9日
    074
  • 游戏匹配实现

    客户端向服务器发送匹配请求,服务端接收后将客户端Session放入匹配队列中,匹配完成时通知用户。 使用观察者设计模式可以实现这个功能。 观察者代码: Java util 包拥有O…

    Java 2023年6月9日
    069
  • nginx url自动加斜杠的问题

    内部服务器使用nginx,做网站测试之用。不同域名使用端口号区分,如www用默认的80端口,其它域名用81,82… 有时直接在地址栏敲网址,会发现跳转到localhos…

    Java 2023年5月30日
    065
  • maven基础

    Powered by 博客园 | | | | 依赖管理 生命周期和插件 发表于2022-07-10 16:59 aurora7301 阅读(6 ) 评论() 编辑 Original…

    Java 2023年6月5日
    072
  • [Java编程思想] 第三章 操作符

    3.1 优先级 请注意,System.out.println()语句中包含”+”操作符,”+”意味着”字符串连接&#82…

    Java 2023年6月5日
    054
  • 《我想进大厂》之Spring夺命连环10问

    摘自:https://www.cnblogs.com/ilovejaney/p/14060895.html 1.说说Spring 里用到了哪些设计模式? 单&…

    Java 2023年5月29日
    083
  • WCF IIS 用户名消息安全 可能碰到的问题

    在IIS 托管WCF其实很简单 在要提供服务的文件目录下新建一个*.SVC,内容类似 Baice.eTerm.SearchService.Service.SearchTicket(…

    Java 2023年6月14日
    050
  • 在Win11的WSL中体验IDEA等GUI程序

    原本已经 放弃WSL很久了,这两天把Win10更新到Win11,看到WSL2已经发布,而且貌似还有很大升级还可以在 WSL中跑Linux GUI应用。在加上Win11的支持简直可以…

    Java 2023年6月16日
    076
  • Redis SCAN命令

    获取指定前缀的key 需求描述: Redis中有大量以xxx开头的key,在不使用keys命令的情况下,如何快速获取这些前缀的key 解决方案: redis自带的scan命令可以解…

    Java 2023年6月7日
    081
  • 类加载器ClassLoader

    1.双亲委派模型 java是根据双亲委派模型的加载类的,当一个类加载器加载类时,会先尝试委托给父类加载器去加载,直到到达启动类加载器顶层若加载不了,则再让子类加载器去加载直到类成功…

    Java 2023年6月9日
    076
  • synchronized 是可重入锁吗?为什么?

    什么是可重入锁? 若一个程序或子程序可以”在任意时刻被中断然后操作系统调度执行另外一段代码,这段代码又调用了该子程序不会出错”,则称其为可重入(reentr…

    Java 2023年6月14日
    064
  • IO(异常处理)

    程序的异常:Throwable* 严重问题:Error 我们不处理。这种问题一般都是很严重的,比如说内存溢出。* 问题:ExceptionA:编译期问题:不是RuntimeExce…

    Java 2023年6月5日
    086
  • SpringBoot-异常处理

    在SpringBoot中,无论是请求不存在的路径、@Valid校验,还是业务代码(Controller、Service、Dao)抛出异常,SpringBoot对错误的默认处理机制是…

    Java 2023年6月6日
    065
  • [已解决] OpenFeign 调用服务提供者出现的BUG

    java.lang.AbstractMethodError: Receiver class org.springframework.cloud.netflix.ribbon.Rib…

    Java 2023年6月15日
    053
  • SpringCloudAlibaba 微服务讲解(一)微服务介绍

    微服务介绍 1.1 系统架构的演变 随若互联网的发展,网站应用的规模也在不断的扩大,逬而导致系统架构也在不断的进行变化.从互联 网早起到现在,系统架构大体经历了下面几个过程:单体应…

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