MyBatis源码剖析之延迟加载源码细节

文章目录

    • 什么是延迟加载?
    • 实现
      • 局部延迟加载
      • 全局延迟加载
    • 延迟加载原理实现
    • 延迟加载原理(源码剖析)
      • Setting 配置加载:
      • 延迟加载代理对象创建
      • 注意事项

什么是延迟加载?

在开发过程中很多时候我们并不需要总是在加载⽤户信息时就⼀定要加载他的订单信息。此时就是我
们所说的延迟加载。
举个栗⼦:

在⼀对多中,当我们有⼀个⽤户,它有个100个订单
在查询⽤户的时候,要不要把关联的订单查出来?
在查询订单的时候,要不要把关联的⽤户查出来?
回答
在查询⽤户时,⽤户下的订单应该是,什么时候⽤,什么时候查询。
在查询订单时,订单所属的⽤户信息应该是随着订单⼀起查询出来。

延迟加载
就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加载。

  • 优点:
    先从单表查询,需要时再从关联表去关联查询,⼤⼤提⾼数据库性能,因为查询单表要⽐关联查询多张表速度要快。

  • 缺点:
    因为只有当需要⽤到数据时,才会进⾏数据库查询,这样在⼤批量数据查询时,因为查询⼯作也要消耗时间,所以可能造成⽤户等待时间变⻓,造成⽤户体验下降。
    在多表中:
    ⼀对多,多对多:通常情况下采⽤延迟加载
    ⼀对⼀(多对⼀):通常情况下采⽤⽴即加载

注意
延迟加载是基于嵌套查询来实现的。

实现

局部延迟加载

associationcollection标签中都有⼀个fetchType属性,通过修改它的值,可以修改局部的加载策略。

<!-- 开启⼀对多 延迟加载 -->
<resultMap id="userMap" type="user"><id column="id" property="id"></id><result column="username" property="username"></result><result column="password" property="password"></result><result column="birthday" property="birthday"></result><!--fetchType="lazy" 懒加载策略fetchType="eager" ⽴即加载策略--><collection property="orderList" ofType="order" column="id" select="com.zjq.dao.OrderMapper.findByUid" fetchType="lazy"></collection>
</resultMap>
<select id="findAll" resultMap="userMap">SELECT * FROM `user`
</select>

全局延迟加载

在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。
在这里插入图片描述

注意:

<!-- 关闭⼀对⼀ 延迟加载 -->
<resultMap id="orderMap" type="order"><id column="id" property="id"></id><result column="ordertime" property="ordertime"></result><result column="total" property="total"></result><!--fetchType="lazy" 懒加载策略fetchType="eager" ⽴即加载策略--><association property="user" column="uid" javaType="user"select="com.zjq.dao.UserMapper.findById" fetchType="eager"></association>
</resultMap>
<select id="findAll" resultMap="orderMap">SELECT * from orders
</select>

延迟加载原理实现

它的原理是,使⽤ CGLIB 或 Javassist( 默认 ) 创建⽬标对象的代理对象。当调⽤代理对象的延迟加载属性的 getting ⽅法时,进⼊拦截器⽅法。⽐如调⽤ a.getB().getName() ⽅法,进⼊拦截器的invoke(…) ⽅法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B对象的 SQL ,把 B 查询上来,然后调⽤a.setB(b) ⽅法,于是 a 对象 b 属性就有值了,接着完成a.getB().getName() ⽅法的调⽤。这就是延迟加载的基本原理
总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定⽅法,执⾏数据加载。

延迟加载原理(源码剖析)

MyBatis延迟加载主要使⽤:JavassistCglib实现,类图展示:
在这里插入图片描述

Setting 配置加载:

public class Configuration {/** aggressiveLazyLoading:* 当开启时,任何⽅法的调⽤都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods).* 默认为true* */protected boolean aggressiveLazyLoading;/*** 延迟加载触发⽅法*/protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));/** 是否开启延迟加载 */protected boolean lazyLoadingEnabled = false;/*** 默认使⽤Javassist代理⼯⼚* @param proxyFactory*/public void setProxyFactory(ProxyFactory proxyFactory) {if (proxyFactory == null) {proxyFactory = new JavassistProxyFactory();}this.proxyFactory = proxyFactory;}//省略...
}

延迟加载代理对象创建

Mybatis的查询结果是由ResultSetHandler接⼝的handleResultSets()⽅法处理的。ResultSetHandler接⼝只有⼀个实现,DefaultResultSetHandler,接下来看下延迟加载相关的⼀个核⼼的⽅法。

  //创建结果对象  private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {this.useConstructorMappings = false; // reset previous mapping resultfinal List<Class<?>> constructorArgTypes = new ArrayList<Class<?>>();final List<Object> constructorArgs = new ArrayList<Object>();//创建返回的结果映射的真实对象Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();for (ResultMapping propertyMapping : propertyMappings) {// issue gcode #109 && issue #149// 判断属性有没配置嵌套查询,如果有就创建代理对象if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {//创建延迟加载代理对象resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);break;}}}this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping resultreturn resultObject;}

默认采⽤javassistProxy进⾏代理对象的创建。

public class Configuration {protected ProxyFactory proxyFactory = new JavassistProxyFactory();
}

JavasisstProxyFactory实现:

public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {/*** 接⼝实现* @param target ⽬标结果对象* @param lazyLoader 延迟加载对象* @param configuration 配置* @param objectFactory 对象⼯⼚* @param constructorArgTypes 构造参数类型* @param constructorArgs 构造参数值* @return*/@Overridepublic Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);}/*** 创建代理对象* @param type* @param callback* @param constructorArgTypes* @param constructorArgs* @return*/  static Object crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {ProxyFactory enhancer = new ProxyFactory();enhancer.setSuperclass(type);try {//通过获取对象⽅法,判断是否存在该⽅法type.getDeclaredMethod(WRITE_REPLACE_METHOD);// ObjectOutputStream will call writeReplace of objects returned by writeReplaceif (log.isDebugEnabled()) {log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this");}//没找到该⽅法,实现接⼝} catch (NoSuchMethodException e) {enhancer.setInterfaces(new Class[]{WriteReplaceInterface.class});} catch (SecurityException e) {// nothing to do here}Object enhanced;Class<?>[] typesArray = constructorArgTypes.toArray(new Class[constructorArgTypes.size()]);Object[] valuesArray = constructorArgs.toArray(new Object[constructorArgs.size()]);try {//创建新的代理对象enhanced = enhancer.create(typesArray, valuesArray);} catch (Exception e) {throw new ExecutorException("Error creating lazy proxy.  Cause: " + e, e);}//设置代理执⾏器((Proxy) enhanced).setHandler(callback);return enhanced;}/*** 内部类代理对象实现,核⼼逻辑执⾏*/private static class EnhancedResultObjectProxyImpl implements MethodHandler {private final Class<?> type;private final ResultLoaderMap lazyLoader;private final boolean aggressive;private final Set<String> lazyLoadTriggerMethods;private final ObjectFactory objectFactory;private final List<Class<?>> constructorArgTypes;private final List<Object> constructorArgs;private EnhancedResultObjectProxyImpl(Class<?> type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {this.type = type;this.lazyLoader = lazyLoader;this.aggressive = configuration.isAggressiveLazyLoading();this.lazyLoadTriggerMethods = configuration.getLazyLoadTriggerMethods();this.objectFactory = objectFactory;this.constructorArgTypes = constructorArgTypes;this.constructorArgs = constructorArgs;}public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {final Class<?> type = target.getClass();EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);//调用外部类的方法Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs);PropertyCopier.copyBeanProperties(type, target, enhanced);return enhanced;}/*** 代理对象执⾏* @param enhanced 原对象* @param method 原对象⽅法* @param methodProxy 代理⽅法* @param args ⽅法参数* @return* @throws Throwable*/@Overridepublic Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {final String methodName = method.getName();try {synchronized (lazyLoader) {if (WRITE_REPLACE_METHOD.equals(methodName)) {Object original;//忽略暂未找到具体作⽤if (constructorArgTypes.isEmpty()) {original = objectFactory.create(type);} else {original = objectFactory.create(type, constructorArgTypes, constructorArgs);}PropertyCopier.copyBeanProperties(type, enhanced, original);if (lazyLoader.size() > 0) {return new JavassistSerialStateHolder(original, lazyLoader.getProperties(), objectFactory, constructorArgTypes, constructorArgs);} else {return original;}} else {//延迟加载数量⼤于0if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {//aggressive ⼀次加载性所有需要要延迟加载属性或者包含触发延迟加载⽅法if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {//⼀次全部加载lazyLoader.loadAll();} else if (PropertyNamer.isSetter(methodName)) {//判断是否为set⽅法,set⽅法不需要延迟加载final String property = PropertyNamer.methodToProperty(methodName);lazyLoader.remove(property);} else if (PropertyNamer.isGetter(methodName)) {final String property = PropertyNamer.methodToProperty(methodName);if (lazyLoader.hasLoader(property)) {//延迟加载单个属性lazyLoader.load(property);}}}}}return methodProxy.invoke(enhanced, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}}} 
}

注意事项

IDEA调试问题: 当配置aggressiveLazyLoading=true,在使⽤IDEA进⾏调试的时候,如果断点打到代理执⾏逻辑当中,你会发现延迟加载的代码永远都不能进⼊,总是会被提前执⾏。 主要产⽣的原因在aggressiveLazyLoading,因为在调试的时候,IDEA的Debuger窗体中已经触发了延迟加载对象的⽅法。

本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位指出。
主页:共饮一杯无的博客汇总👨‍💻

保持热爱,奔赴下一场山海。🏃🏃🏃

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/16283.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android 开发代码规范

一. AndroidStudio开发工具规范 使用最新的稳定版本.统一文件的编码格式为utf-8. 清除每个类里面的无效的import导包.代码样式统一,比如&#xff0c;tab缩进4个空格&#xff0c;或者 tab size等如果没有特殊情况使用默认的配置即可。每行字数每行字符数不得超过 160 字符&…

【100天精通python】Day20:文件及目录操作_os模块和os.psth模块,文件权限修改

目录 专栏导读 1 文件的目录操作 os模块的一些操作目录函数​编辑 os.path 模块的操作目录函数 2 相对路径和绝对路径 3 路径拼接 4 判断目录是否存在 5 创建目录、删除目录、遍历目录 专栏导读 专栏订阅地址&#xff1a;https://blog.csdn.net/qq_35831906/category_12…

Mysql-MVCC 并发版本控制

参考链接&#xff1a;一文读懂MVCC实现原理_Nicolos_Z的博客-CSDN博客 1.总述&#xff1a; MVCC 主要是InnoDB解决数据库事务读写&#xff0c;导致的脏读、重复读问题的处理方法。通过快照读的方式&#xff0c;提高数据库并发查询的能力。 2.MVCC的实现 实现MVCC主要用到了…

在OK3588板卡上部署模型实现人工智能OCR应用

一、主机模型转换 我们依旧采用FastDeploy来部署应用深度学习模型到OK3588板卡上 进入主机Ubuntu的虚拟环境 conda activate ok3588 安装rknn-toolkit2&#xff08;该工具不能在OK3588板卡上完成模型转换&#xff09; git clone https://github.com/rockchip-linux/rknn-to…

金蝶云星空任意文件读取漏洞复现(0day)

0x01 产品简介 金蝶云星空是一款云端企业资源管理&#xff08;ERP&#xff09;软件&#xff0c;为企业提供财务管理、供应链管理以及业务流程管理等一体化解决方案。金蝶云星空聚焦多组织&#xff0c;多利润中心的大中型企业&#xff0c;以 “开放、标准、社交”三大特性为数字…

ChatGPT伦理挑战:人工智能的权利与责任

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

HCIP期中实验

考试需求 1 、该拓扑为公司网络&#xff0c;其中包括公司总部、公司分部以及公司骨干网&#xff0c;不包含运营商公网部分。 2 、设备名称均使用拓扑上名称改名&#xff0c;并且区分大小写。 3 、整张拓扑均使用私网地址进行配置。 4 、整张网络中&#xff0c;运行 OSPF 协议…

实时协作:团队效率倍增的关键

实时协作是指团队在当前时刻共同完成项目的能力。无论是否使用技术&#xff0c;都能实现这一点。然而&#xff0c;随着远程工作的盛行&#xff0c;安全的协作工具被用来让团队成员在项目和一般业务之间保持联系和同步。 传统协作与实时协作的区别 两种类型的协作最明显的区别…

uniapp 微信小程序 navigationBarBackgroundColor 标题栏颜色渐变

大体思路&#xff1a; 第一步&#xff1a;“navigationStyle”:“custom” 第二步&#xff1a; template内 重点&#xff1a;给view添加ref“top” 第三步&#xff1a;添加渐变色样式 1、pages.json {"path" : "pages/user/user","style" : …

【玩转Linux】Linux输入子系统简介

(꒪ꇴ꒪ ),hello我是祐言博客主页&#xff1a;C语言基础,Linux基础,软件配置领域博主&#x1f30d;快上&#x1f698;&#xff0c;一起学习&#xff01;送给读者的一句鸡汤&#x1f914;&#xff1a;集中起来的意志可以击穿顽石!作者水平很有限&#xff0c;如果发现错误&#x…

拯救者Y9000K无线Wi-Fi有时不稳定?该如何解决?

由于不同品牌路由器的性能差异&#xff0c;无法完美兼容最新的无线网卡技术&#xff0c;在连接网络时&#xff08;特别是网络负载较大的情况下&#xff09;&#xff0c;可能会出现Wi-Fi信号断开、无法网络无法访问、延迟突然变大的情况&#xff1b;可尝试下面方法进行调整。 1…

前端小练-仿掘金导航栏

文章目录 前言项目结构导航实现创作中心移动小球消息提示 完整代码 前言 闲的&#xff0c;你信嘛&#xff0c;还得开发一个基本的门户社区网站&#xff0c;来给到Hlang,不然我怕说工作量不够。那么这个的话&#xff0c;其实也很好办&#xff0c;主要是这个门户网站的UI写起来麻…

pytest study

pytest 测试用例的识别与运行 测试文件&#xff1a;test_*.py 和 *_test.py 以test开头或结尾的文件 测试用例&#xff1a;Test*类包含的所有 test_*的方法&#xff08;测试类不能带有__init__方法&#xff09;&#xff0c; 不在class中的所有test_*的方法 def func(x):r…

web-文件上传和upload-labs靶场通关

目录 前端过滤 uploads-lab-1: 后端验证 upload-labs-2&#xff1a;mime验证 upload-labs-3&#xff1a;黑名单绕过 upload-labs-4&#xff1a;黑名单绕过-.htaccess(这里得用旧版的phpstudy&#xff0c;新版的不行) upload-labs-5&#xff1a;后缀名绕过 upload-labs-…

Android Ble蓝牙App(一)扫描

Ble蓝牙App&#xff08;一&#xff09;扫描 前言正文一、基本配置二、扫描准备三、扫描页面① 增加UI布局② 点击监听③ 扫描处理④ 广播处理 四、权限处理五、扫描结果① 列表适配器② 扫描结果处理③ 接收结果 六、源码 前言 关于低功耗的蓝牙介绍我已经做过很多了&#xff0…

form-data 提交文件请求远程调用

文件请求方法 /*** 上传图文消息内的图片 获取url* 富文本内的图片** param file*/public static String uploadMediaGetUrl(File file) throws IOException {if (!file.exists()) {return null;}String responseData null;try {String url "http://localhost:8503/fil…

Linux NUMA架构(非统一内存访问)

NUMA架构 NUMA Architecture| Non Uniform Memory Access Policy/Model | Numa Node Configuration (CPU Affinity) NUMA架构产生的原因 cpu的高速处理功能和内存存储直接的速度会严重影响cpu的性能。传统的计算机单核架构,cpu通过内存总线(内存访问控制器)直接连接到一…

全网最全讲的最详细的多线程原理

在我们开始讲多线程之前&#xff0c;我们先来了解一下什么是进程&#xff0c;什么是线程。进程和线程是操作系统中两个容易混淆的概念。 进程 在Windows操作系统中打开任务管理器&#xff0c;可以查看进程和线程的详细信息。也可以使用专业的进程查看小软件——Process Explo…

javascript实现几何粒子星空连线背景效果

javascript实现几何粒子星空连线背景效果 <html><head><meta charset"UTF-8"><title>几何星空连线背景</title><script src"./ParticleBackground.js"></script> </head><body><canvas id"…

Linux进程调度

初探Linux进程调度 已知&#xff1a;父进程创建子进程后&#xff0c;父子进程同时运行。 问题&#xff1a;如果计算机只有一个处理器&#xff0c;父子进程以什么方式同时执行&#xff1f; 基本概念 运行&#xff1a;一个可执行程序从文件&#xff0c;变成进程的过程。 执行…