欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

mybatis延迟加载原理(mybatis工作原理及流程)

程序员文章站 2023-11-29 14:19:22
1. 目的本文主要解读mybatis 延迟加载实现原理2. 延迟加载如何使用setting 参数配置设置参数描述有效值默认值lazyloadingenabled延迟加载的全局开关。当开启时,所有关联对...

1. 目的

本文主要解读mybatis 延迟加载实现原理

2. 延迟加载如何使用

setting 参数配置

设置参数描述有效值默认值lazyloadingenabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置fetchtype属性来覆盖该项的开关状态。true、
falsefalseaggressivelazyloading当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyloadtriggermethods).true、falsefalse (true in ≤3.4.1)lazyloadtriggermethods指定哪个对象的方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashcode,tostring

配置

<configuration>
 <settings>
 <!-- 开启延迟加载 -->
 <setting name="lazyloadingenabled" value="true" />
 <setting name="aggressivelazyloading" value="false" />
 <setting name="lazyloadtriggermethods" value="equals,clone,hashcode,tostring" />
 </settings>
</configuration>

mapper 配置

<mapper namespace="org.apache.ibatis.submitted.lazy_properties.mapper">
 
 <resultmap type="org.apache.ibatis.submitted.lazy_properties.user"
 id="user">
 <id property="id" column="id" />
 <result property="name" column="name" />
 </resultmap>
 <!-- 结果对象 -->
 <resultmap type="org.apache.ibatis.submitted.lazy_properties.user" id="userwithlazyproperties" extends="user">
 <!-- 延迟加载对象lazy1 -->
 <association property="lazy1" column="id" select="getlazy1" fetchtype="lazy" />
 <!-- 延迟加载对象lazy2 -->
 <association property="lazy2" column="id" select="getlazy2" fetchtype="lazy" />
 <!-- 延迟加载集合lazy3 --> 
 <collection property="lazy3" column="id" select="getlazy3" fetchtype="lazy" />
 </resultmap>
 <!-- 执行的查询 -->
 <select id="getuser" resultmap="userwithlazyproperties">
 select * from users where id = #{id}
 </select>
</mapper>

user 实体对象

public class user implements cloneable {
 private integer id;
 private string name;
 private user lazy1;
 private user lazy2;
 private list<user> lazy3;
 public int settercounter;
 
 省略...
 }

执行解析:

  • 调用getuser查询数据,从查询结果集解析数据到user对象,当数据解析到lazy1,lazy2,lazy3判断需要执行关联查询
  • lazyloadingenabled=true,将创建lazy1,lazy2,lazy3对应的proxy延迟执行对象lazyloader,并保存
  • 当逻辑触发lazyloadtriggermethods 对应的方法(equals,clone,hashcode,tostring)则执行延迟加载
  • 如果aggressivelazyloading=true,只要触发到对象任何的方法,就会立即加载所有属性的加载

3. 延迟加载原理实现

延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。

mybatis延迟加载主要使用:javassist,cglib实现,类图展示:

mybatis延迟加载原理(mybatis工作原理及流程)

4. 延迟加载源码解析

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;
 }
 
 //省略...
}

延迟加载代理对象创建

defaultresultsethandler

//#mark 创建结果对象
 private object createresultobject(resultsetwrapper rsw, resultmap resultmap, resultloadermap lazyloader, string columnprefix) throws sqlexception {
 this.useconstructormappings = false; // reset previous mapping result
 final list<class<?>> constructorargtypes = new arraylist<class<?>>();
 final list<object> constructorargs = new arraylist<object>();
 //#mark 创建返回的结果映射的真实对象
 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()) {
 //#mark 创建延迟加载代理对象
 resultobject = configuration.getproxyfactory().createproxy(resultobject, lazyloader, configuration, objectfactory, constructorargtypes, constructorargs);
 break;
 }
 }
 }
 this.useconstructormappings = resultobject != null && !constructorargtypes.isempty(); // set current mapping result
 return resultobject;
 }

代理功能实现

由于javasisst和cglib的代理实现基本相同,这里主要介绍javasisst

proxyfactory接口定义

public interface proxyfactory {

void setproperties(properties properties);

/**

* 创建代理

* @param target 目标结果对象

* @param lazyloader 延迟加载对象

* @param configuration 配置

* @param objectfactory 对象工厂

* @param constructorargtypes 构造参数类型

* @param constructorargs 构造参数值

* @return

*/

object createproxy(object target, resultloadermap lazyloader, configuration configuration, objectfactory objectfactory, list<class<?>> constructorargtypes, list<object> constructorargs);

}

javasisstproxyfactory实现

public class javassistproxyfactory implements org.apache.ibatis.executor.loader.proxyfactory {
 
 /**
 * 接口实现
 * @param target 目标结果对象
 * @param lazyloader 延迟加载对象
 * @param configuration 配置
 * @param objectfactory 对象工厂
 * @param constructorargtypes 构造参数类型
 * @param constructorargs 构造参数值
 * @return
 */
 @override
 public 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);
 }
 //省略...
 
 /**
 * 代理对象实现,核心逻辑执行
 */
 private static class enhancedresultobjectproxyimpl implements methodhandler {
 
 /**
 * 创建代理对象
 * @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 writereplace
 if (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;
 }
 
 
 /**
 * 代理对象执行
 * @param enhanced 原对象
 * @param method 原对象方法
 * @param methodproxy 代理方法
 * @param args 方法参数
 * @return
 * @throws throwable
 */
 @override
 public 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 {
 //延迟加载数量大于0
 if (lazyloader.size() > 0 && !finalize_method.equals(methodname)) {
 //aggressive 一次加载性所有需要要延迟加载属性或者包含触发延迟加载方法
 if (aggressive || lazyloadtriggermethods.contains(methodname)) {
 log.debug("==> laze lod trigger method:" + methodname + ",proxy method:" + methodproxy.getname() + " class:" + enhanced.getclass());
 //一次全部加载
 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);
 log.debug("load one :" + methodname);
 }
 }
 }
 }
 }
 return methodproxy.invoke(enhanced, args);
 } catch (throwable t) {
 throw exceptionutil.unwrapthrowable(t);
 }
 }
 }

5. 注意事项

  1. idea调试问题 当配置aggressivelazyloading=true,在使用idea进行调试的时候,如果断点打到代理执行逻辑当中,你会发现延迟加载的代码永远都不能进入,总是会被提前执行。 主要产生的原因在aggressivelazyloading,因为在调试的时候,idea的debuger窗体中已经触发了延迟加载对象的方法。

如图:调试还未进入lazyloader.loadall(); 实际日志已经显示延迟加载已经完成,代码与日志通过颜色区分。

mybatis延迟加载原理(mybatis工作原理及流程)