精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载

时间:2021-07-10 20:25:11

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

MyBatis 版本:3.5.2

MyBatis-Spring 版本:2.0.3

MyBatis-Spring-Boot-Starter 版本:2.1.4

MyBatis的SQL执行过程

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

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

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

精尽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 方法时,才会触发其对应的子查询,获取到查询结果,设置该对象的属性值

在上一篇《SQL执行过程(三)之ResultSetHandler》文档中讲到

  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<Object> list = selectList();
// <2> 提取结果
resultObject = resultExtractor.extractObjectFromList(list, targetType);
// <3> 返回结果
return resultObject;
}

selectList方法

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

	private <E> List<E> 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<Object> 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<String, LoadPair> 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<String> 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<Class<?>> constructorArgTypes,
List<Object> constructorArgs);
}
  • 就定义了一个createProxy创建动态代理对象的方法,交由不同的子类去实现

实现类如下图所示:

精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载

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

// Configuration.java
protected 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)方法

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

@Override
public Object createProxy(Object target, ResultLoaderMap lazyLoader, Configuration configuration,
ObjectFactory objectFactory, List<Class<?>> constructorArgTypes, List<Object> 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<Class<?>> constructorArgTypes,
List<Object> 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<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;
}
}
  • 我们主要看到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<Class<?>> constructorArgTypes, List<Object> 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方法增强器的而实现方法,代理对象的方法都会进入这个方法

@Override
public 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 源码分析》