
本文共 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
执行器去执行,过程大致如下:
- 通过
DefaultSqlSessionFactory
创建与数据库交互的SqlSession
“会话”,其内部会创建一个Executor
执行器对象 - 然后
Executor
执行器通过StatementHandler
创建对应的java.sql.Statement
对象,并通过ParameterHandler
设置参数,然后执行数据库相关操作 - 如果是数据库更新操作,则可能需要通过
KeyGenerator
先设置自增键,然后返回受影响的行数 - 如果是数据库查询操作,则需要将数据库返回的
ResultSet
结果集对象包装成ResultSetWrapper
,然后通过DefaultResultSetHandler
对结果集进行映射,最后返回 Java 对象
上面还涉及到一级缓存、二级缓存和延迟加载等其他处理过程
SQL执行过程(四)之延迟加载
在前面SQL执行过程一系列的文档中,已经详细地分析了在 MyBatis 的SQL执行过程中,SqlSession 会话将数据库相关操作交由 Executor 执行器去完成,通过 StatementHandler 去执行数据库的操作,并获取到数据库的执行结果,如果是查询结果则通过 DefaultResultSetHandler 对结果集进行映射,转换成 Java 对象
其中 MyBatis 也提供了延迟加载的功能,当调用实体类需要延迟加载的属性的 getter 方法时,才会触发其对应的子查询,获取到查询结果,设置该对象的属性值
在上一篇文档中讲到
如果存在嵌套子查询且需要延迟加载,则会通过
ProxyFactory
动态代理工厂,为返回结果的实例对象创建一个动态代理对象(Javassist),也就是说返回结果实际上是一个动态代理对象可以回到上一篇文档的4.2.1createResultObject方法小节第
4
步看看resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs;
后续属性映射的过程中,如果该属性是嵌套子查询并且需要延迟加载,则会创建一个
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 TypeboundSql
:SQL相关信息resultExtractor
:查询结果的抽取器loaded
:是否已经加载
loadResult方法
loadResult()
方法,延迟加载的执行器的执行方法,获取到查询结果,并提取出结果,方法如下:
public Object loadResult() throws SQLException { // <1> 查询结果 List
selectList方法
selectList()
方法,执行延迟加载对应的子查询,获取到查询结果,方法如下:
privateList 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); } } }
- 获得 Executor 执行器,如果当前线程不是创建 ResultLoader 对象时所在的线程的,或者这个执行器被关闭了,那么需要调用
newExecutor()
方法创建一个新的执行器 - 通过该执行器进行数据的查询,并返回查询结果
- 如果这个执行器是新创建的,则需要关闭它
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<Object> list
查询结果提取数据,转换成目标类型,有以下四种场景:
List类型,则直接返回
集合类型,则为该集合类型创建一个实例对象,并把
list
全部添加到该对象中,然后返回数组类型
- 获取数组的成员类型
- 创建数组对象,并设置大小
- 如果是基本类型则一个一个添加到数组中,否则直接将
list
转换成数组,然后返回
其他类型,也就是一个实体类了,直接获取
list
中的第一个元素返回(如果list
集合的个数大于1则抛出异常)
ResultLoaderMap
org.apache.ibatis.executor.loader.ResultLoaderMap
:用于保存某个对象中所有的延迟加载
构造方法
public class ResultLoaderMap { /** * 用于延迟加载的加载器 * key:属性名称 * value:ResultLoader 加载器的封装对象 LoadPair */ private final MaploaderMap = 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));}
如果
property
属性名称包含.
点,且最前面一部分已经有对应的延迟加载对象了,则出现重复添加,需要抛出异常将入参信息封装成
LoadPair
对象,并放入loaderMap
中关于
LoadPair
,是ResultLoaderMap的一个内部类,里面有对序列化进行处理,最后还是调用ResultLoader
的load()
方法,这里就不列出来了
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;}
- 先将该属性对应的延迟加载从
loaderMap
集合中删除 - 然后调用
LoadPair
的load()
方法,触发延迟加载,并设置查询结果设置到对象的属性中
loadAll方法
loadAll()
方法,用于触发所有还没加载的延迟加载,方法如下:
public void loadAll() throws SQLException { final SetmethodNameSet = 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
- 就定义了一个
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
内部直接调用EnhancedResultObjectProxyImpl
的createProxy
方法
crateProxy静态方法
crateProxy(Class<?> type, MethodHandler callback, List<Class<?>> constructorArgTypes, List<Object> constructorArgs)
方法
用于创建一个动态代理的实例对象,并设置MethodHandler
方法增强器,方法如下:
static Object crateProxy(Class type, MethodHandler callback, List> constructorArgTypes, List
- 创建 ProxyFactory 动态代理对象工厂
- 设置父类,需要代理的类对象
- 设置和序列化相关配置
- 创建动态代理实例对象,传入代理类对象的构造方法的入参类型数组和入参数组
- 设置动态代理实例对象的
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 SetlazyLoadTriggerMethods; private final ObjectFactory objectFactory; private final List > constructorArgTypes; private final List
- 我们主要看到
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;}
这个方法在JavassistProxyFactory
的createProxy
方法被调用,然后自己内部又调用JavassistProxyFactory
的静态createProxy
方法,这里我已经按序号标明了步骤
- 创建
EnhancedResultObjectProxyImpl
方法的增强器callback
- 创建动态代理实例对象,并设置方法的增强器为
callback
,调用的是上面的静态createProxy
方法 - 将
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
关键字,保证线程安全
如果加强的方法是
writeReplace
,则进行一些序列化相关的操作,暂不分析,其实是没看懂~如果
lazyLoader
中有延迟加载的属性,并且加强的方法不是finalize
- 如果开启了任一方法的调用都会加载该对象的所有延迟加载属性,或者是
equals clone hashCode toString
其中的某个方法,则触发所有的延迟加载 - 否则,如果是属性的setter方法,则从
lazyLoader
中将该属性的延迟加载删除(如果存在),因为主动设置了这个属性值,则需要取消该属性的延迟加载 - 否则,如果是属性的getter方法,则执行延迟加载(会将结果设置到该对象的这个属性中),里面也会从
lazyLoader
中将该属性的延迟加载删除
- 如果开启了任一方法的调用都会加载该对象的所有延迟加载属性,或者是
继续执行原方法
到这里,延迟加载已经实现了
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 执行过程我们已经全部分析完了,其中肯定有不对或者迷惑的地方,欢迎指正!!!感谢大家的阅读!!!😄😄😄
参考文章:芋道源码
发表评论
最新留言
关于作者
