MyBatis从入门到精通—源码剖析之延迟加载源码细节

网友投稿 498 2022-10-30

MyBatis从入门到精通—源码剖析之延迟加载源码细节

MyBatis从入门到精通—源码剖析之延迟加载源码细节

什么是延迟加载?

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

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

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

优点:

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

缺点:

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

实现

局部延迟加载

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

全局延迟加载

在Mybatis的核⼼配置⽂件中可以使⽤setting标签修改全局的加载策略。

注意:

延迟加载原理实现

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

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

MyBatis延迟加载主要使⽤:Javassist,Cglib实现,类图展示:

Setting 配置加载:

public class Configuration { /** aggressiveLazyLoading: * 当开启时,任何⽅法的调⽤都会加载该对象的所有属性。否则,每个属性会按需加载(参考 lazyLoadTriggerMethods). * 默认为true * */ protected boolean aggressiveLazyLoading; /** * 延迟加载触发⽅法 */ protected Set lazyLoadTriggerMethods = new HashSet(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 result final List> constructorArgTypes = new ArrayList>(); final List constructorArgs = new ArrayList(); //创建返回的结果映射的真实对象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final List 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 result return 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 */ @Override public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List 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> constructorArgTypes, List 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; } /** * 内部类代理对象实现,核⼼逻辑执⾏ */ private static class EnhancedResultObjectProxyImpl implements MethodHandler { private final Class type; private final ResultLoaderMap lazyLoader; private final boolean aggressive; private final Set lazyLoadTriggerMethods; private final ObjectFactory objectFactory; private final List> constructorArgTypes; private final List constructorArgs; private EnhancedResultObjectProxyImpl(Class type, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List> constructorArgTypes, List 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> constructorArgTypes, List 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 */ @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)) { //⼀次全部加载 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窗体中已经触发了延迟加载对象的⽅法。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SkyWalking:一个分布式跟踪系统和APM(应用程序性能监视器)
下一篇:moviemon:Python应用程序能在命令行中显示你电影的所有信息
相关文章

 发表评论

暂时没有评论,来抢沙发吧~