JPA/hibernate懒加载原理分析及JSON格式API反序列化时连环触发懒加载问题的解决

article/2025/6/18 10:35:34

       什么是懒加载

       JPA是java持久层的API,也就是java官方提供的一个ORM框架,Spring data jpa是spring基于hibernate开发的一个JPA框架。Spring data jpa提供了大量的数据库操作接口,以及采用动态代理的方式做的以接口方法命名的数据库操作方式,大大简化了开发人员对数据库操作的代码。它对于简单的查询操作非常简便,但是一旦涉及到复杂的查询或许就会非常复杂,比如动态字段查询、多表联合查询等,所以很多技术平台都采用了spring data jpa+mybatis的模式,即spring data jpa做简单的查询操作,mybatis做复杂的数据库操作。但是这种模式让我觉得更复杂,因为对于同一张表得提供不同的数据库操作bean,所以我对spring data jpa又做了一层封装,在不去改变源码的基础上,让jpa对于复杂的查询也能非常简便,但这不属于本文讨论的内容,有兴趣的同学可以关注我的公众号,我会在后续文章中专门做深入的讲解剖析。

       所谓的懒加载模式就是只有用到的时候才会去触发加载,不用到的时候就不会触发,在ORM框架中即可以理解为当查询某张表A的数据时,这张表有通过某个字段关联映射到另外一张表B的数据,当需要用到表B的数据时,才以A的关联字段的值去查询表B的相应的数据,如果不需要表B的数据则不用查询,因此懒加载不但简化了代码并且提高了系统性能。至于详细的懒加载用法我就不在此讨论了,网上书上资料非常多,也不属于本文讨论内容。

 

       懒加载原理剖析

       懒加载的大致原理是Spring data jpa在sql查询完成数据映射时,将被标记为懒加载的字段(本文仅讨论一对多、多对多的情况,所以该字段为应为集合)转换成了PersistentCollection类型,我们查看源码或debug跟踪代码即可看到List类型被转换为PersistentBag类型、ArrayList类型被转换为PersistentList类型、HashMap类型被转换为PersistentMap类型等,从图中我们可以看到这些类型都属于PersistentCollection的子类。

       从上图我们可以看到PersistentBag、PersistentList、PersistentMap这些类在实例化时将hibernate的数据库操作核心session做为参数传进来了,你可能会说哪有session啊,那明明是SessionImplementor,我就不多做解释了,我只能说SessionImplementor这个接口是hibernate专门用来抽象为实现org.hibernate.Session这个标准接口所要做的一些子功能。也就是说它本身就包含了数据库的连接、预定义好的sql查询等信息,只等着我们去触发了。

       我们现在知道了懒加载其实是用了“偷梁换柱”的理念,将集合字段替换为自己对应好的类型(如何映射会在后续文章中继续剖析),这个类型中包含了数据库的连接、预定义好的sql,只等着我们主动去触发了,但是如何触发?你可能要说了,我还是按照正常的方式去调用的这个字段,我怎么不知道什么时候触发它了,怎么触发的?对于懒加载字段我们要使用它的时候主要就是在数据返回的时候,也就是页面渲染或者API数据返回。你可能会问那我通过set调用给其他对象的值行吗?不行,在一个事务内hibernate会报集合引用错误,如果事务已经关闭,连接已经释放,也可能会引起其他异常,除非hibernate支持另启动一个外部事务,那仍然会报集合引用错误,所以不可能将懒加载字段赋给其他对象并进行引用。

       现在我们继续讨论该如何触发懒加载,继续查看源码,以PersistentBag为例,发现PersistentBag改写了toString方法,他调用了read方法,而toString方法就是在Debug时、页面渲染或打印中要用到的,这个read方法才是真正的调用入口。read方法中调用了initialize方法进行初始化,完成之后会调用endRead方法,并且将initialized变量设置为true,说明该懒加载字段已经被触发了。我们现在可以梳理通了,在去打印或者渲染懒加载字段时通过toString方法来触发懒加载的执行。

       对于页面渲染部分我们分析完了,那么RestApi是如何触发的呢?RestApi数据返回一般采用json的形式,Spring boot中默认采用jackson进行序列化,我们就以jackson进行简单的分析。跟踪源代码可以看到,jackson在对collection进行序列化时首先调用了size方法来返回总的数据条数,而PersistentBag重写了size方法,在readSize方法中我们又看到了read方法的身影,所有的思路都串起来了!

       我们再来总结一下Spring data jpa懒加载原理,首先它在查询完数据库将数据映射到bean字段时做了偷梁换柱,将集合字段替换为自己组装好的集合对象,并且将hibernate操作数据库的核心Session缓存了进来。在获取该懒加载字段的值时,通过toString或者size方法调用read方式来触发数据库的执行,从而完成对字段懒加载的调用。

 

       API接口序列化时懒加载被连环触发的问题

       现在我们知道懒加载的执行原理了,那么问题来了,在提供API方法时我们通常采用json进行序列化,在json序列化时会循环深度的去调用对象的值来组装json数据,现在假如有一个数据对象A,其中包含对象B,关系是一对多,而对象B又包含C,C又包含D等,都是一对多或多对多关系,在序列化A时取B的数据就牵引到C又牵引到D等,就会引起连环触发,你可能并不需要B或C或D,但是他们确实由于序列化的原因被连环触发了!由于我们平台使用的是jackson进行json序列化,所以我就以jackson为例来提供解决方案了。我们的方案既要保证没有被调用的懒加载字段不被执行,也要保证被调用过的懒加载字段不能被执行。怎样判断懒加载字段是否已经被执行了呢?从上面的分析我们可以看到被执行的懒加载字段的PersistentBag中的初始化字段initialized会被设置为true。那问题就好解决了,我们可以重写序列化方式,判断懒加载字段是否被触发,如果API需要返回某个懒加载字段我们可以通过自己写工具类主动进行触发。

    上述解决方案中涉及到了jackson的自定义序列化、反射及缓存(反射的性能)等相关内容,如果您有不明白的可以通过留言或者关注公众号同我交流,我也会在后续文章中继续提供相关的解决方案,敬请持续关注!


如需获取更多精彩内容或者技术探讨,请扫码关注本人公众号!爱生活,爱代码,代码也是码,我是爱码三疯!

爱码三疯公众号:hlt0912_dyh


http://chatgpt.dhexx.cn/article/W7vxGUy9.shtml

相关文章

react性能优化-懒加载原理

编译阶段的优化 开发阶段构建更快 loader的include和exclude属性 {test: /.(j|t)sx?$/,use: [{loader: "thread-loader",},{loader: "babel-loader",options: {presets: [["babel/preset-env", { modules: false }], //es6->es5"babe…

js图片懒加载原理、实现及节流优化

1.懒加载原理 在图片没有进入可视区域时&#xff0c;先给的src一个默认加载的图片&#xff0c;这样浏览器就不会发送请求了&#xff0c;等到图片进入可视区域再把真实的图片路径data-src给src。 2.具体实现 1. 效果 2. 代码如下&#xff1a; <style>.imgList{width:…

html图片懒加载,图片懒加载原理及实现

原理&#xff1a; 先将img标签的src链接设为同一张图片(比如空白图片)&#xff0c;然后给img标签设置自定义属性(比如 data-src),然后将真正的图片地址存储在data-src中&#xff0c;当JS监听到该图片元素进入可视窗口时&#xff0c;将自定义属性中的地址存储到src属性中。达到懒…

java懒加载的原理_每天使用 Spring 框架,那你知道 lazy-init 懒加载原理吗?

普通的bean的初始化是在容器启动初始化阶段执行的&#xff0c;而被lazy-init修饰的bean 则是在从容器里第一次进行context.getBean(“”)时进行触发。 Spring 启动的时候会把所有bean信息(包括XML和注解)解析转化成Spring能够识别的BeanDefinition并存到Hashmap里供下面的初始化…

mybatis -- 懒加载原理

目录 测试代码调试代码为什么BlogResp2是代理对象呢? 什么时候创建的代理对象呢? 让我们看一下源码懒加载的赋值流程懒加载失效的原因blogResp2的代理对象是如何构建lazyLoader属性的blogResp2的代理对象结构 测试代码 通过id 查询博客信息, 同时懒加载查询博客的所有评论信息…

js实现图片懒加载原理

有时候一个网页会包含很多的图片,例如淘宝京东这些购物网站,商品图片多只之又多,页面图片多,加载的图片就多。服务器压力就会很大。不仅影响渲染速度还会浪费带宽。比如一个1M大小的图片,并发情况下,达到1000并发,即同时有1000个人访问,就会产生1个G的带宽。 为了解决…

【转载】懒加载原理

https://blog.csdn.net/w1418899532/article/details/90515969 有时候一个网页会包含很多的图片&#xff0c;例如淘宝京东这些购物网站&#xff0c;商品图片多只之又多&#xff0c;页面图片多&#xff0c;加载的图片就多。服务器压力就会很大。不仅影响渲染速度还会浪费带宽。…

JavaScript中的懒加载——概念,作用,原理,实现步骤,以及3种原生js实现方式

1.什么是懒加载&#xff1f; 懒加载也就是延迟加载。 当访问一个页面的时候&#xff0c;先把img元素或是其他元素的背景图片路径替换成一张大小为1*1px图片的路径&#xff08;这样就只需请求一次&#xff0c;俗称占位图&#xff09;&#xff0c; 只有当图片出现在浏览器的可…

懒加载的原理及实现

1.懒加载概念 对于页面有很多静态资源的情况下&#xff08;比如网商购物页面&#xff09;&#xff0c;为了节省用户流量和提高页面性能&#xff0c;可以在用户浏览到当前资源的时候&#xff0c;再对资源进行请求和加载。 2.懒加载实现原理 2.1监听onscroll事件判断资源位置 …

为什么单线程比多线程快

首先我们先介绍一下什么是进程什么是线程 进程&#xff1a; 当一个程序开始运行时&#xff0c;它就是一个进程&#xff0c;进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。 线程&#xff1a; 是进程的一个执行单元&#xff0c;是…

Redis6.0新特性、剖析线程模型(单线程和多线程)

一. Redis6.0 新特性 1. 多线程IO redis6.0引入多线程IO&#xff0c;只是用来处理网络数据的读写和协议的解析&#xff0c;而执行命令依旧是单线程&#xff0c;所以不需要去考虑set/get、事务、lua等的并发问题。&#xff08;详细的线程模型见后面&#xff09; 2. ACL精细化权…

「Redis线程模型」Redis的单线程与多线程

「Redis线程模型」Redis的单线程与多线程 文章目录 「Redis线程模型」Redis的单线程与多线程[toc]Redis 是单线程吗&#xff1f;Redis 单线程模式是怎样的&#xff1f;Redis 采用单线程为什么还这么快&#xff1f;Redis 6.0 之前为什么使用单线程&#xff1f;Redis 6.0 之后为什…

Redis 是单线程的正确理解

一、为什么Redis是单线程的 1️⃣官方答案 因为 Redis 是基于内存的操作&#xff0c;CPU不是 Redis 的瓶颈。Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现&#xff0c;而且 CPU 不会成为瓶颈&#xff0c;那就顺理成章地采用单线程的方案了。 2️⃣…

Python单线程/多线程

Python里的多线程是假的多线程&#xff0c;不管有多少核&#xff0c;同一时间只能在一个核中进行操作&#xff01; 利用Python的多线程&#xff0c;只是利用CPU上下文切换的优势&#xff0c;看上去像是并发&#xff0c;其实只是单线程。 import threading import timedef tes…

为什么 Redis 是单线程的

文章目录 3.6 为什么 Redis 是单线程的3.6.1 Redis的单线程理解3.6.2 单线程的 Redis 为何高并发快 3.6 为什么 Redis 是单线程的 参考地址&#xff1a;https://blog.csdn.net/ChineseSoftware/article/details/122562476 官方答案 因为 Redis 是基于内存的操作&#xff0c;CP…

Python的单线程和多线程

1.发展背景 2.进程和线程的区别 线程是程序执行的最小单位&#xff0c;而进程是操作系统分配资源的最小单位&#xff1b;一个进程由一个线程组成&#xff0c;线程是一个进程中代码的不同执行路线&#xff1b;进程之间相互独立&#xff0c;但同一进程下的各个线程之间共享程序的…

JavaNIO——单线程(笔记)

文章目录 一、 三大组件1.1 Channel & Buffer1.2 Selector 二、 ByteBuffer字节缓存2.1 结构2.2 堆内存与直接内存2.3 读与写2.4 Scattering Reads与Gathering Writes2.5 简单处理黏包与半包 三、FileChannel文件编程3.1 读取3.2 写入3.3 关闭3.4 位置3.5 大小3.6 强制写入…

单线程简介

单线程顾名思义&#xff0c;就是只有一个线程&#xff0c;默认情况下&#xff0c;系统为应用程序分配一个主线程&#xff0c;该线程执行程序中以Main方法开始和结束的代码。线程具有生命周期&#xff0c;它包含3个状态&#xff0c;分别为出生状态、就绪状态和运行状态。 出生状…

小米更新显示非官方rom_MIUI官改篇对比分析-极光ROM-台湾W大-星空未来-其他官改官网...

说起安卓刷机,最有趣味性的就是小米手机了,能解锁BL,能ROOT,基本成功刷机 主流机型,而小米的开源也确确实实得到了认可。小米刷机基本分出2条线路,一个 是第三方ROM,一条是官方修改版ROM,今天ROM乐园小编就和大家分析下以下常 见的几个官方修改版ROM:极光ROM-台湾W大-…

安卓rom制作教程_MIUI官改篇对比分析-极光ROM-台湾W大-星空未来-其他官改官网

说起安卓刷机,最有趣味性的就是小米手机了,能解锁BL,能ROOT,基本成功刷机 主流机型,而小米的开源也确确实实得到了认可。小米刷机基本分出2条线路,一个 是第三方ROM,一条是官方修改版ROM,今天ROM乐园小编就和大家分析下以下常 见的几个官方修改版ROM:极光ROM-台湾W大-…