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

亦余心之所善兮,虽九死其犹未悔。这篇文章主要讲述MyBatis从入门到精通—源码剖析之延迟加载源码细节相关的知识,希望能为你提供帮助。
什么是延迟加载?在开发过程中很多时候我们并不需要总是在加载?户信息时就?定要加载他的订单信息。此时就是我
们所说的延迟加载。
举个栗?:
延迟加载
就是在需要?到数据时才进?加载,不需要?到数据时就不加载数据。延迟加载也称懒加载。

  • 优点:
先从单表查询,需要时再从关联表去关联查询,??提?数据库性能,因为查询单表要?关联查询多张表速度要快。
  • 缺点:
因为只有当需要?到数据时,才会进?数据库查询,这样在?批量数据查询时,因为查询?作也要消耗时间,所以可能造成?户等待时间变?,造成?户体验下降。
在多表中:
?对多,多对多:通常情况下采?延迟加载
?对?(多对?):通常情况下采??即加载
注意:
延迟加载是基于嵌套查询来实现的。
实现 局部延迟加载
在association和collection标签中都有?个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标签修改全局的加载策略。
< settings> < !--开启全局延迟加载功能--> < setting name="lazyLoadingEnabled" value="https://www.songbingjia.com/android/true"/> < /settings>

注意:
< !-- 关闭?对? 延迟加载 --> < 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延迟加载主要使?:Javassist,Cglib实现,类图展示:
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 result final 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 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< 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 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 hereObject 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 */ @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窗体中已经触发了延迟加载对象的?法。
【MyBatis从入门到精通—源码剖析之延迟加载源码细节】
MyBatis从入门到精通—源码剖析之延迟加载源码细节

文章图片


    推荐阅读