由于做数据脱敏时,其中一个方案为使用Mybatis Interceptor,因此本篇介绍下Mybatis的执行流程,看下有哪些核心的类和方法,以及拦截器是如何切入SQL的执行流程的;
1. 整体流程这篇文章纯粹是为了更好的理顺mybatis的执行流程,执行流程具体可参考下面的流程图,mybatis的代码基于3.4.6;
先给结论,将整体流程及涉及的类、类之间的调用关系整理为流程图:
通过上面的图,可以知道:
2. 源码分析
- 核心流程一是指mapperMethod的执行流程,这个过程主要是指对Mapper接口的方法参数的解析;
- 核心流程二是指executor的执行流程,这个过程主要是指StatementHandler的创建,包含:BoundSql 的创建(返回SQL语句)、parameterHandler(执行SQL前的SQL参数处理器)和ResultSetHandler(SQL结果赋值到ResutSets时的值处理)的创建;
- StatementHandler执行流程,这部分主要执行 SQL 并对结果集进行处理;
- 通过上面的流程,了解了一些关键操作的执行顺序:(1)Mapper接口参数的组装在 exector 之前前,同样在所有的拦截器执行前;(2)BoundSql生成SQL语句的时机在于 statementHandler 的创建,早于parameterHandler和ResultSetHandler的时机;
(1)Mapper接口代理的工厂——MapperProxyFactory
(2)Mapper接口代理类——MapperProxy
- MapperMethod的对象初始化时,填充SqlSession、Mapper接口、接口内的方法;
- MapperProxy的invoke方法的核心在于创建MapperMethod对象,并通过执行mapperMethod的execute方法继续执行;
(3)Mapper接口方法——MapperMethod
- MapperMethod初始化时,构建SQL命令SqlCommand和方法签名参数MethodSignature;
- MethodSignature的convertArgsToSqlCommandParam完成mybatis从方法参数到SQL命令参数的转换。
- execute方法实际是执行DefaultSqlSession的update/insert/select/delete等操作;
- SQL命令参数的解析convertArgsToSqlCommandParam在真正执行 Executor 方法之前;
(4)Mapper接口方法签名——MethodSignature
- MethodSignature的convertArgsToSqlCommandParam方法,实际上是调用ParamNameResolver的getNameParams方法;
(5)Mapper接口方法参数名解析——ParamNameResolver
Mybatis的参数传递情况分为:一个参数、Map参数、javaBean参数、多个参数、Collection参数、List参数、Array数组参数;这个类负责解析Mapper接口的方法参数;(解析的规则示例详细可参考:Mybatis第三篇:参数解析)
ParamNameResolver的getNameParams方法:
通过上述代码可知:
- MapperMethod执行SQL操作时,通过MethodSignature的convertArgsToSqlCommandParam解析参数,后续以解析后的方法参数来生成SQL语句并执行SQL语句;
- MethodSignature构建被调用的方法的签名信息,即通过ParamNameResolver来构建方法参数的别名信息,包含2步:(1)解析方法参数的别名:负责解析方法的参数和参数注解,构建参数下标及对应的别名的映射关系;(2)构建方法参数对象:结合参数别名映射关系,构建参数别名和参数对象的映射关系;
实际上Mapper方法参数的解析除了根据注解别名来构建参数名和参数对象的映射关系外,针对集合类也做了特殊处理(org.apache.ibatis.session.defaults.DefaultSqlSession#wrapCollection),在后面介绍相关代码;
这里补充参数解析的示例:
场景一: List
(6)MapperMethod中执行SQL的对象——SqlSession
上面分析完了MapperMethod的invoke方法的第1步:convertArgsToSqlCommandParam方法的实现,现在回到invoke方法的第2步:通过SqlSession的方法来执行SQL;
DefaultSqlSession是SqlSession的默认实现类,以下是SqlSession的select和update方法:
通过上述代码可知:
- DefaultSqlSession的select和update等方法实际上是调用Executor来执行的;
(7)DefaultSqlSession执行前获取的MappedStatement对象
MappedStatement维护了Mapper接口对应的Mapper.xml中的一条
MappedStatement的核心方法——getBoundSql
(8)DefaultSqlSession通过Executor执行增删改查
执行Executor的方法前先做一步专门针对集合类的参数解析:
集合类的参数解析代码如下:
- DefaultSqlSession#wrapCollection的过程在MethodSignature#convertArgsToSqlCommandParam之后,针对只有一个参数对象且没有@Param标注别名的集合类对象会构建一个Map进行返回。
- 回忆下mybatis的xml定义中经常会遍历列表对象,存在
的语句,这里的list就是在这个过程中生成的。
Executor从CachingExecutor到SimpleExecutor的顺序进行执行,核心在SimpleExecutor当中:
- Executor从CachingExecutor到SimpleExecutor的顺序进行执行,核心在SimpleExecutor当中;
- SimpleExecutor#doQuery的核心流程包括:1.创建StatementHandler对象;2.参数初始化prepareStatement;3.执行StatementHandler的 query 方法;
- 创建StatementHandler的过程中完成了 BoundSql 的执行、parameterHandler和 ResultSetHandler 的创建;这一点很重要!!!
- prepareStatement内部会执行parameterHandler的方法;
- 注意点:BoundSql的生成早于prepareStatement方法的执行,因此 mybatis 原有的顺序必然导致parameterHandler的执行结果无法影响 BoundSql 的生成,只能在执行SQL之前,做一些诸如通过parameterHandler影响参数值的事情;
(9)获取StatementHandler——拦截器的织入
可以看到,拦截器就是这里织入的,这也是走读Mybatis执行过程需要了解的一个点,也就是我们可以通过注册拦截器,在真正执行SQL之前,做一些自定义的操作;
(10)再回到handler执行增删改查的位置——根据类型路由RoutingStatementHandler
这里switch进入PreparedStatementHandler:
PreparedStatementHandler的父类——BaseStatementHandler
通过上面的代码可知:
- RoutingStatementHandler内部创建的是PreparedStatementHandler。
- PreparedStatementHandler的创建中包含:boundSql的生成、parameterHandler和resultSetHandler的创建。
PreparedStatementHandler的执行后针对 update 操作后会进行主键的设置(通过KeyGenerator设置);针对 select 操作后执行resultSetHandler的执行动作;
至此Mybatis的执行过程的源码走读已经结束;
参考:
mybatis涉及的核心类Mappedstatement
mybatis笔记-MappedStatement
详述mybatis执行流程 - 简书



