Mybatis中延迟加载和懒加载

结果集的手动映射和自动映射

在mybatis中,对于结果集的映射方式有2冲,第一种是手动映射,也就是自己写ResultMap进行查询字段和Bean中属性的映射,第二种是自动映射,也就是ResultType直接设置Bean的class值或者别名Mybatis会自动对应字段和属性Bean的值(其实自动映射最后也会转换为手动映射)。

1、自动映射:在平时工作中使用的比较多的就是自动映射。

 2、手动映射:在手动映射中,一个ResultMap每隔属性都会对应一条ResultMapping,其中比较不常用的就是association和collection两种。

① 一对一属性映射:可直接使用result属性bean.属性进行映射,也可以使用 association 进行映射

② 一对多属性映射:使用collection进行映射

PS:关于这几个标签的使用可以看一下这篇帖子:传送门

当出现嵌套查询的时候,第一层返回值BeanA存在属性BeanB,而第二层返回值BeanB存在属性BeanA,这个时候在嵌套查询的时候,就可能出现死循环,也就是循环依赖问题,在Mybatis中采用延迟加载(一级缓存)和缓存占位符的方式进行处理。

下面是测试代码

 

PS:注意在生成BEAN的get和set方法的时候,不能使用lombok的@Data标签,否则还是出现循环依赖的问题。

还有一种情况就是懒加载,懒加载的意思就是只用调用某个属性的get方法的时候,采去加载数据,这样保证只有使用的时候数据才进行加载不使用不去加载,在一定程度上提高了效率。在Mybatis中,手动映射结果集的时候才会存在懒加载。

下面分别讲解一下延迟加载和懒加载的实现逻辑。

 下面就是嵌套查询的源码:

延迟加载

延迟加载就是在主查询执行完毕之后,对命中一级缓存(key)的数据(property->resultObject)进行填充

 在BaseExecutor中实现延迟加载,可以看到当可以加载的时候,才能加载,否则加入到队列中

 当我们在调用查询的时候,可以看到有一个queryStack,它是记录了嵌套查询的层数,当等于0的时候,也就是最后的查询,把全部延迟加载队列中的数据进行加载。

 下面来DBUG一下代码:

在第一层查询的时候,用占位符进行站位:

在解析第一层的结果的时候,会触发第二层的SQL查询:

 这时候queryStack的值变为2:

 在解析第二层的数据的时候,因为Blog评论里面有博客对象,所以触发到的缓存,进行延迟加载:

 因为第一层查询的是占位符,所以放入延迟队列中:

 当queryStack为0的时候,把所有延迟加载队列中的数据全部加载

同时可以看到,只有在最后一层的时候,才对缓存进行清空,也也说明了之前说的所谓的一级缓存关闭,只是减小了作用域,同时可以看出,一级缓存在嵌套子查询中起到很大的作用。

关键点:Mybatis解决嵌套查询的循环依赖采用的是延迟加载+缓存占位符(一级缓存) 

懒加载

懒加载主要采用动态代理实现的,就是在原始的Bean做一层代理,这样在调用get方法的时候,可以操作自己想操作的。

代理完之后的对象,存在一个handler,指向一个执行器,进行数据的查询。

当调用set之后再调用get的时候,get请求是不会覆盖set请求的数据的。

 当期望序列化\反序列化之后也触发懒加载的时候,需要满足两个条件:

① 必须使用jdk原生的序列化方式

② 必须实现一个配置工厂类,同时在setting中注册这个类,并不需要实现上面业务逻辑

上面简单说了懒加载的实现,下面看看内部原理,同deug看下源码:

简单说明一下上述结构的功能:

① MethodHandler:处理个个属性get方法,就是对get方法进行增强

② ResultLoaderMap:存放还未进行懒加载的属性,当进行完懒加载的时候,就进行移除

③ LoadPair:执行序列化\反序列化操作

④ ResultLoader:最终执行查询数据库操作

下面debug一下代码,可以看到,还没调用getCommentList的时候,就触发了懒加载,这是因为在debug模式下,会调用toString方法,有四种方法会自动触发懒加载,下面loadMap为0的主要原因是因为toString已经进行懒加载,导致没有属性进行懒加载了。

把实现懒加载的方法设置为空:

重新debug发现,这个时候就不会出现触发懒加载了。

 对照上面的结构图可以看出来代理对象的结构:

在JavassistProxyFactory的invoke方法中对代理对象进行增强,首先判断是否开启懒加载,然后判断是方法是触发了全部懒加载,然后再判断是否调用了set方法,最后才是进行懒加载。

 先移除map中的属性,在调用load

整个逻辑分为2部分,第一部分是判断是否是通过metaResultObject和resultLoader两个属性是否为空判断是否是序列化,第二部分是直接查询数据库设置数据

 可以看到 serializationCheck 这个关键字没有什么用,因为上面根据两个对象已经判断出是否是反序列化的了。

同时在初始化resultLoader的时候,传入一个ClosedExecutor,证明这个执行器执行完毕后就关闭了。

触发位置,当创建结果集的时候,判断有子查询同时懒加载就进行代理对象的创建:

上面讲述了懒加载的基本实现流程,下面总结一下:懒加载的时候,就是使用动态代理对象对其进行增强,只有调用get方法的时候,才进行懒加载。同时set之后的数据不会被懒加载覆盖,序列化和懒加载同时实现需要条件。

wirteReplace和readResolve

主要用于在序列化和反序列化之前,对bean的操作,可以更改属性值,甚至更改bean的类型。

 

这里推荐mybatis讲解非常棒的福利,这里所有的Mybatis都是跟鲁班大叔学习的:B站 传送门 真正的干货满满!~

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值