精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载
发布日期:2021-05-09 04:36:21 浏览次数:16 分类:博客文章

本文共 18970 字,大约阅读时间需要 63 分钟。

该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(、、)进行阅读

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

该系列其他文档请查看:

MyBatis的SQL执行过程

在前面一系列的文档中,我已经分析了 MyBatis 的基础支持层以及整个的初始化过程,此时 MyBatis 已经处于就绪状态了,等待使用者发号施令了

那么接下来我们来看看它执行SQL的整个过程,该过程比较复杂,涉及到二级缓存,将返回结果转换成 Java 对象以及延迟加载等等处理过程,这里将一步一步地进行分析:

MyBatis中SQL执行的整体过程如下图所示:

在 SqlSession 中,会将执行 SQL 的过程交由Executor执行器去执行,过程大致如下:

  1. 通过DefaultSqlSessionFactory创建与数据库交互的 SqlSession “会话”,其内部会创建一个Executor执行器对象
  2. 然后Executor执行器通过StatementHandler创建对应的java.sql.Statement对象,并通过ParameterHandler设置参数,然后执行数据库相关操作
  3. 如果是数据库更新操作,则可能需要通过KeyGenerator先设置自增键,然后返回受影响的行数
  4. 如果是数据库查询操作,则需要将数据库返回的ResultSet结果集对象包装成ResultSetWrapper,然后通过DefaultResultSetHandler对结果集进行映射,最后返回 Java 对象

上面还涉及到一级缓存二级缓存延迟加载等其他处理过程

SQL执行过程(四)之延迟加载

在前面SQL执行过程一系列的文档中,已经详细地分析了在 MyBatis 的SQL执行过程中,SqlSession 会话将数据库相关操作交由 Executor 执行器去完成,通过 StatementHandler 去执行数据库的操作,并获取到数据库的执行结果,如果是查询结果则通过 DefaultResultSetHandler 对结果集进行映射,转换成 Java 对象

其中 MyBatis 也提供了延迟加载的功能,当调用实体类需要延迟加载的属性的 getter 方法时,才会触发其对应的子查询,获取到查询结果,设置该对象的属性值

在上一篇文档中讲到

  1. 如果存在嵌套子查询且需要延迟加载,则会通过ProxyFactory动态代理工厂,为返回结果的实例对象创建一个动态代理对象(Javassist),也就是说返回结果实际上是一个动态代理对象

    可以回到上一篇文档的4.2.1createResultObject方法小节第4步看看

    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,                                                            objectFactory, constructorArgTypes, constructorArgs;
  2. 后续属性映射的过程中,如果该属性是嵌套子查询并且需要延迟加载,则会创建一个ResultLoader对象添加到上面的ResultLoaderMap对象lazyLoader

    可以回到上一篇文档的4.2.4.2getNestedQueryMappingValue方法小节第6步看看

    final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,                                                    nestedQueryParameterObject, targetType, key, nestedBoundSql);if (propertyMapping.isLazy()) { // <6.2> 如果要求延迟加载,则延迟加载    // <6.2.1> 如果该属性配置了延迟加载,则将其添加到 `ResultLoader.loaderMap` 中,等待真正使用时再执行嵌套查询并得到结果对象    lazyLoader.addLoader(property, metaResultObject, resultLoader);    // <6.2.2> 返回延迟加载占位符    value = DEFERRED;} else { // <6.3> 如果不要求延迟加载,则直接执行加载对应的值    value = resultLoader.loadResult();}

那么接下来我们来看看 MyBatis 中的延迟加载是如何实现的

ResultLoader

org.apache.ibatis.executor.loader.ResultLoader:延迟加载的加载器,在上面你可以看到需要延迟加载的属性会被封装成该对象

构造方法

public class ResultLoader {    /**     * 全局配置对象     */	protected final Configuration configuration;    /**     * 执行器     */	protected final Executor executor;    /**     * MappedStatement 查询对象     */	protected final MappedStatement mappedStatement;	/**	 * 查询的参数对象	 */	protected final Object parameterObject;	/**	 * 目标的类型,返回结果的 Java Type	 */	protected final Class
targetType; /** * 实例工厂 */ protected final ObjectFactory objectFactory; protected final CacheKey cacheKey; /** * SQL 相关信息 */ protected final BoundSql boundSql; /** * 结果抽取器 */ protected final ResultExtractor resultExtractor; /** * 创建 ResultLoader 对象时,所在的线程的 id */ protected final long creatorThreadId; /** * 是否已经加载 */ protected boolean loaded; /** * 查询的结果对象 */ protected Object resultObject; public ResultLoader(Configuration config, Executor executor, MappedStatement mappedStatement, Object parameterObject, Class
targetType, CacheKey cacheKey, BoundSql boundSql) { this.configuration = config; this.executor = executor; this.mappedStatement = mappedStatement; this.parameterObject = parameterObject; this.targetType = targetType; this.objectFactory = configuration.getObjectFactory(); this.cacheKey = cacheKey; this.boundSql = boundSql; this.resultExtractor = new ResultExtractor(configuration, objectFactory); this.creatorThreadId = Thread.currentThread().getId(); }}

主要包含以下信息:

  • executor:执行器
  • mappedStatement:查询语句的MappedStatement对象
  • parameterObject:子查询的入参
  • targetType:返回结果的Java Type
  • boundSql:SQL相关信息
  • resultExtractor:查询结果的抽取器
  • loaded:是否已经加载

loadResult方法

loadResult()方法,延迟加载的执行器的执行方法,获取到查询结果,并提取出结果,方法如下:

public Object loadResult() throws SQLException {    // <1> 查询结果    List list = selectList();    // <2> 提取结果    resultObject = resultExtractor.extractObjectFromList(list, targetType);    // <3> 返回结果    return resultObject;}

selectList方法

selectList()方法,执行延迟加载对应的子查询,获取到查询结果,方法如下:

private 
List
selectList() throws SQLException { // <1> 获得 Executor 对象 Executor localExecutor = executor; if (Thread.currentThread().getId() != this.creatorThreadId || localExecutor.isClosed()) { // 创建一个的 Executor 对象,保证线程安全 localExecutor = newExecutor(); } try { // <2> 执行查询 return localExecutor.query(mappedStatement, parameterObject, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, cacheKey, boundSql); } finally { // <3> 关闭 Executor 对象 if (localExecutor != executor) { localExecutor.close(false); } } }
  1. 获得 Executor 执行器,如果当前线程不是创建 ResultLoader 对象时所在的线程的,或者这个执行器被关闭了,那么需要调用newExecutor()方法创建一个新的执行器
  2. 通过该执行器进行数据的查询,并返回查询结果
  3. 如果这个执行器是新创建的,则需要关闭它

newExecutor方法

newExecutor()方法,创建一个新的Executor执行器用于执行延迟加载的子查询,执行完后需要关闭,方法如下:

private Executor newExecutor() {    // 校验 environment    final Environment environment = configuration.getEnvironment();    if (environment == null) {        throw new ExecutorException("ResultLoader could not load lazily.  Environment was not configured.");    }    // 校验 DataSource    final DataSource ds = environment.getDataSource();    if (ds == null) {        throw new ExecutorException("ResultLoader could not load lazily.  DataSource was not configured.");    }    // 创建 Transaction 对象    final TransactionFactory transactionFactory = environment.getTransactionFactory();    final Transaction tx = transactionFactory.newTransaction(ds, null, false);    // 创建 Executor 对象    return configuration.newExecutor(tx, ExecutorType.SIMPLE);}

ResultExtractor

org.apache.ibatis.executor.ResultExtractor:结果提取器,用于提取延迟加载对应的子查询的查询结果,转换成Java对象,代码如下:

public class ResultExtractor {    /**     * 全局配置对象     */	private final Configuration configuration;    /**     * 实例工厂     */	private final ObjectFactory objectFactory;	public ResultExtractor(Configuration configuration, ObjectFactory objectFactory) {		this.configuration = configuration;		this.objectFactory = objectFactory;	}	/**     * 从 list 中,提取结果     *     * @param list list     * @param targetType 结果类型     * @return 结果     */	public Object extractObjectFromList(List list, Class
targetType) { Object value = null; /* * 从查询结果中抽取数据转换成目标类型 */ if (targetType != null && targetType.isAssignableFrom(list.getClass())) { // <1> 场景1,List 类型 // 直接返回 value = list; } else if (targetType != null && objectFactory.isCollection(targetType)) { // <2> 场景2,集合类型 // <2.1> 创建集合的实例对象 value = objectFactory.create(targetType); // <2.2> 将结果添加到其中 MetaObject metaObject = configuration.newMetaObject(value); // <2.3> 将查询结果全部添加到集合对象中 metaObject.addAll(list); } else if (targetType != null && targetType.isArray()) { // <3> 场景3,数组类型 // <3.1> 获取数组的成员类型 Class
arrayComponentType = targetType.getComponentType(); // <3.2> 创建数组对象,并设置大小 Object array = Array.newInstance(arrayComponentType, list.size()); if (arrayComponentType.isPrimitive()) { // <3.3> 如果是基本类型 for (int i = 0; i < list.size(); i++) { // 一个一个添加到数组中 Array.set(array, i, list.get(i)); } value = array; } else { // <3.4> 将 List 转换成 Array value = list.toArray((Object[]) array); } } else { // <4> 场景4 if (list != null && list.size() > 1) { throw new ExecutorException("Statement returned more than one row, where no more than one was expected."); } else if (list != null && list.size() == 1) { // 取首个结果 value = list.get(0); } } return value; }}

List<Object> list查询结果提取数据,转换成目标类型,有以下四种场景:

  1. List类型,则直接返回

  2. 集合类型,则为该集合类型创建一个实例对象,并把list全部添加到该对象中,然后返回

  3. 数组类型

    1. 获取数组的成员类型
    2. 创建数组对象,并设置大小
    3. 如果是基本类型则一个一个添加到数组中,否则直接将list转换成数组,然后返回
  4. 其他类型,也就是一个实体类了,直接获取list中的第一个元素返回(如果list集合的个数大于1则抛出异常)

ResultLoaderMap

org.apache.ibatis.executor.loader.ResultLoaderMap:用于保存某个对象中所有的延迟加载

构造方法

public class ResultLoaderMap {    /**   	 * 用于延迟加载的加载器   	 * key:属性名称   	 * value:ResultLoader 加载器的封装对象 LoadPair   	 */	private final Map
loaderMap = new HashMap<>();}

addLoader方法

addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader)方法,用于添加一个需要延迟加载属性

入参分别表示:需要延迟加载的属性名称、该属性所在的Java对象(也就是查询返回的结果对象)、延迟加载对应的加载器,方法如下:

public void addLoader(String property, MetaObject metaResultObject, ResultLoader resultLoader) {  	// 获取第一个属性名称    String upperFirst = getUppercaseFirstProperty(property);    if (!upperFirst.equalsIgnoreCase(property) && loaderMap.containsKey(upperFirst)) {        throw new ExecutorException("省略...");    }    loaderMap.put(upperFirst, new LoadPair(property, metaResultObject, resultLoader));}
  1. 如果property属性名称包含.点,且最前面一部分已经有对应的延迟加载对象了,则出现重复添加,需要抛出异常

  2. 将入参信息封装成LoadPair对象,并放入loaderMap

    关于LoadPair,是ResultLoaderMap的一个内部类,里面有对序列化进行处理,最后还是调用ResultLoaderload()方法,这里就不列出来了

load方法

load(String property)方法,用于触发该属性的延迟加载,方法如下:

public boolean load(String property) throws SQLException {    LoadPair pair = loaderMap.remove(property.toUpperCase(Locale.ENGLISH));    if (pair != null) {        pair.load();        return true;    }    return false;}
  1. 先将该属性对应的延迟加载从loaderMap集合中删除
  2. 然后调用LoadPairload()方法,触发延迟加载,并设置查询结果设置到对象的属性中

loadAll方法

loadAll() 方法,用于触发所有还没加载的延迟加载,方法如下:

public void loadAll() throws SQLException {    final Set
methodNameSet = loaderMap.keySet(); String[] methodNames = methodNameSet.toArray(new String[methodNameSet.size()]); for (String methodName : methodNames) { load(methodName); }}

ProxyFactory

org.apache.ibatis.executor.loader.ProxyFactory:动态代理工厂接口

public interface ProxyFactory {  default void setProperties(Properties properties) {    // NOP  }  Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,                      ObjectFactory objectFactory, List
> constructorArgTypes, List
constructorArgs);}
  • 就定义了一个createProxy创建动态代理对象的方法,交由不同的子类去实现

实现类如下图所示:

回到Configuration全局配置对象中,你会发现默认使用的是JavassistProxyFactory实现类

// Configuration.javaprotected ProxyFactory proxyFactory = new JavassistProxyFactory();

JavassistProxyFactory

org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory:实现ProxyFactory接口,基于javassist(一个开源的分析、编辑和创建Java字节码的类库)创建动态代理对象

构造方法

public class JavassistProxyFactory implements org.apache.ibatis.executor.loader.ProxyFactory {	private static final String FINALIZE_METHOD = "finalize";	private static final String WRITE_REPLACE_METHOD = "writeReplace";	public JavassistProxyFactory() {		try {			// 加载 javassist.util.proxy.ProxyFactory 类			Resources.classForName("javassist.util.proxy.ProxyFactory");		} catch (Throwable e) {			throw new IllegalStateException(					"Cannot enable lazy loading because Javassist is not available. Add Javassist to your classpath.",					e);		}	}}
  • 加载 javassist.util.proxy.ProxyFactory

createProxy方法

createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)方法

创建动态代理对象的入口,方法如下:

@Overridepublic Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,        ObjectFactory objectFactory, List
> constructorArgTypes, List
constructorArgs) { // <1> 创建动态代实例对象 return EnhancedResultObjectProxyImpl.createProxy(target, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);}

内部直接调用EnhancedResultObjectProxyImplcreateProxy方法

crateProxy静态方法

crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) 方法

用于创建一个动态代理的实例对象,并设置MethodHandler方法增强器,方法如下:

static Object crateProxy(Class
type, MethodHandler callback, List
> constructorArgTypes, List
constructorArgs) { // <3.1> 创建 ProxyFactory 动态代理对象工厂 ProxyFactory enhancer = new ProxyFactory(); // <3.2> 设置父类,需要代理的类对象 enhancer.setSuperclass(type); // <3.3> 和序列化相关 try { // 获取需要代理的类对象中的 writeReplace 方法 type.getDeclaredMethod(WRITE_REPLACE_METHOD); // ObjectOutputStream will call writeReplace of objects returned by writeReplace if (LogHolder.log.isDebugEnabled()) { LogHolder.log.debug(WRITE_REPLACE_METHOD + " method was found on bean " + type + ", make sure it returns this"); } } catch (NoSuchMethodException e) { // 如果没有 writeReplace 方法,则设置接口为 WriteReplaceInterface 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 { // <3.4> 创建动态代理实例对象 enhanced = enhancer.create(typesArray, valuesArray); } catch (Exception e) { throw new ExecutorException("Error creating lazy proxy. Cause: " + e, e); } // <3.5> 设置动态代理实例对象的 MethodHandler 方法增强器 ((Proxy) enhanced).setHandler(callback); return enhanced;}
  1. 创建 ProxyFactory 动态代理对象工厂
  2. 设置父类,需要代理的类对象
  3. 设置和序列化相关配置
  4. 创建动态代理实例对象,传入代理类对象的构造方法的入参类型数组和入参数组
  5. 设置动态代理实例对象的MethodHandler方法增强器

EnhancedResultObjectProxyImpl

JavassistProxyFactory的内部类,动态代理对象的MethodHandler方法增强器

构造方法
private static class EnhancedResultObjectProxyImpl implements MethodHandler {    private final Class
type; private final ResultLoaderMap lazyLoader; /** * 开启时,任一方法的调用都会加载该对象的所有延迟加载属性,默认false */ 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; }}
  • 我们主要看到ResultLoaderMap lazyLoader属性,里面保存了需要延迟加载的属性和加载器
createProxy方法

createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration, ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)方法

创建动态代理实例对象,方法如下:

public static Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,        ObjectFactory objectFactory, List
> constructorArgTypes, List
constructorArgs) { final Class
type = target.getClass(); // <2> 创建方法的增强器 EnhancedResultObjectProxyImpl callback = new EnhancedResultObjectProxyImpl(type, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); // <3> 创建动态代理实例对象,设置方法的增强器 Object enhanced = crateProxy(type, callback, constructorArgTypes, constructorArgs); // <4> 将 target 的属性值复制到 enhanced 动态代实例对象中 PropertyCopier.copyBeanProperties(type, target, enhanced); return enhanced;}

这个方法在JavassistProxyFactorycreateProxy方法被调用,然后自己内部又调用JavassistProxyFactory的静态createProxy方法,这里我已经按序号标明了步骤

  1. 创建EnhancedResultObjectProxyImpl方法的增强器callback
  2. 创建动态代理实例对象,并设置方法的增强器为callback,调用的是上面的静态createProxy方法
  3. target的属性值复制到enhanced动态代实例对象中
invoke方法

javassist.util.proxy.MethodHandler方法增强器的而实现方法,代理对象的方法都会进入这个方法

@Overridepublic Object invoke(Object enhanced, Method method, Method methodProxy, Object[] args) throws Throwable {    final String methodName = method.getName();    try {        synchronized (lazyLoader) {            // <1> 如果方法名为 writeReplace,和序列化相关            if (WRITE_REPLACE_METHOD.equals(methodName)) {                Object original;                if (constructorArgTypes.isEmpty()) {                    original = objectFactory.create(type);                } else {                    original = objectFactory.create(type, constructorArgTypes, constructorArgs);                }                // 从动态代理实例对象中复制属性值到 original 中                PropertyCopier.copyBeanProperties(type, enhanced, original);                if (lazyLoader.size() > 0) {                    return new JavassistSerialStateHolder(original, lazyLoader.getProperties(),                                                           objectFactory,constructorArgTypes, constructorArgs);                } else {                    return original;                }            } else { // <2> 加载延迟加载的属性                if (lazyLoader.size() > 0 && !FINALIZE_METHOD.equals(methodName)) {                    // <2.1> 如果开启了任一方法的调用都会加载该对象的所有延迟加载属性,或者是 "equals", "clone", "hashCode", "toString" 其中的某个方法                    if (aggressive || lazyLoadTriggerMethods.contains(methodName)) {                      	// 加载所有延迟加载的属性                        lazyLoader.loadAll();                    } else if (PropertyNamer.isSetter(methodName)) {                      	// <2.2> 如果为 setter 方法,从需要延迟加载属性列表中移除                        final String property = PropertyNamer.methodToProperty(methodName);                        lazyLoader.remove(property);                    } else if (PropertyNamer.isGetter(methodName)) {                      	// <2.3> 如果调用了 getter 方法,则执行延迟加载,从需要延迟加载属性列表中移除                        final String property = PropertyNamer.methodToProperty(methodName);                        if (lazyLoader.hasLoader(property)) {                          // 加载该属性值                            lazyLoader.load(property);                        }                    }                }            }        }        // <3> 继续执行原方法        return methodProxy.invoke(enhanced, args);    } catch (Throwable t) {        throw ExceptionUtil.unwrapThrowable(t);    }}

先给ResultLoaderMap lazyLoader添加synchronized关键字,保证线程安全

  1. 如果加强的方法是writeReplace,则进行一些序列化相关的操作,暂不分析,其实是没看懂~

  2. 如果lazyLoader中有延迟加载的属性,并且加强的方法不是finalize

    1. 如果开启了任一方法的调用都会加载该对象的所有延迟加载属性,或者是equals clone hashCode toString其中的某个方法,则触发所有的延迟加载
    2. 否则,如果是属性的setter方法,则从lazyLoader中将该属性的延迟加载删除(如果存在),因为主动设置了这个属性值,则需要取消该属性的延迟加载
    3. 否则,如果是属性的getter方法,则执行延迟加载(会将结果设置到该对象的这个属性中),里面也会从lazyLoader中将该属性的延迟加载删除
  3. 继续执行原方法

到这里,延迟加载已经实现了

CglibProxyFactory

org.apache.ibatis.executor.loader.cglib.CglibProxyFactory:实现ProxyFactory接口,基于cglib(一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口)创建动态代理对象

实现方式和JavassistProxyFactory类似,这里就不进行分析了,感兴趣的可以看一下

总结

本文分析了 MyBatis 中延迟加载的实现方法,在 DefaultResultSetHandler 映射结果集的过程中,如果返回对象有属性是嵌套子查询,且需要延迟加载,则通过JavassistProxyFactory为返回结果创建一个动态代理对象,并设置MethodHandler方法增强器为EnhancedResultObjectProxyImpl对象

其中传入ResultLoaderMap对象,该对象保存了这个结果对象中所有的ResultLoader延迟加载

EnhancedResultObjectProxyImpl中拦截结果对象的方法,进行增强处理,通过ResultLoader延迟加载器获取到该属性值,然后从ResultLoaderMap中删除,在你调用该属性的getter方法时才加载数据,这样就实现了延迟加载

好了,对于 MyBatis 的整个 SQL 执行过程我们已经全部分析完了,其中肯定有不对或者迷惑的地方,欢迎指正!!!感谢大家的阅读!!!😄😄😄

参考文章:芋道源码

上一篇:精尽MyBatis源码分析 - SqlSession 会话与 SQL 执行入口
下一篇:精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler

发表评论

最新留言

关注你微信了!
[***.104.42.241]2025年04月06日 09时51分28秒