MyBatis源码剖析-数据处理
# 1. MyBatis架构原理
# 1.1 MyBatis整体架构
MyBatis 的整体架构分为三层, 分别是框架支持层、数据处理层和接口层,如下图所示:

- API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。MyBatis和数据库的交互有两种方式:传统的MyBatis提供的API及Mapper代理的方式
- 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
- 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
# 1.2 主要构件及其相互关系
下图展示了 MyBatis 执行一条 SQL 语句的大致过程:

| 构件 | 描述 |
|---|---|
| SqlSession | 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能 |
| Executor | MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护 |
| StatementHandler | 封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换成List集合。 |
| ParameterHandler | 负责对用户传递的参数转换成JDBC Statement所需要的参数 |
| ResultSetHandler | 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合; |
| TypeHandler | 负责java数据类型和jdbc数据类型之间的映射和转换 |
| MappedStatement | MappedStatement维护了一条select、update、delete、insert节点的封装 |
| SqlSource | 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回 |
| BoundSql | 表示动态生成的SQL语句以及相应的参数信息 |
# 2. 传统方式源码剖析
案例代码如下所示:
@Test
public void testFindAll() throws Exception{
// 加载配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 解析配置文件
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行查询并封装结果集
List<Emp> all = sqlSession.selectList("com.hukai.demo.dao.IEmpDao.findAll");
all.forEach(System.out::println);
sqlSession.close();
}
2
3
4
5
6
7
8
9
10
11
12
13
# 2.1 初始化
# 2.1.1 解析核心配置文件
第一步,通过资源加载模块加载配置文件,解析器模块解析 XML 文件,生成 Configuration 对象。
// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream) {
//调用了重载方法
return build(inputStream, null, null);
}
// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建 XMLConfigBuilder, XMLConfigBuilder是专门解析mybatis的配置文件的类
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 执行 XML 解析
// 创建 DefaultSqlSessionFactory 对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
/**
* 创建 DefaultSqlSessionFactory 对象
*
* @param config Configuration 对象
* @return DefaultSqlSessionFactory 对象
*/
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config); //构建者设计模式
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
通过这段代码我们得知,SqlSessionFactoryBuilder对象将加载的字节流交给了XMLConfigBuilder对象,由XMLConfigBuilder负责解析配置文件并封装到Configuration对象中,并通过这个Configuration对象生成了默认的SqlSessionFactory。
Configuration对象的结构和xml配置文件的对象几乎相同,回顾一下xml中的配置标签有哪些:
properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理 器),objectFactory (对象工厂),mappers (映射器)等 Configuration也有对应的对象属性来封装它 们
也就是说,初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部属性中。
接下来我们看看XMLConfigBuilder具体是如何解析配置文件的:
/**
* 解析 XML 成 Configuration 对象。
*
* @return Configuration 对象
*/
public Configuration parse() {
// 若已解析,抛出 BuilderException 异常
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 标记已解析
parsed = true;
///parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
// 解析 XML configuration 节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
/**
* 解析 XML
*
* 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
*
* @param root 根节点
*/
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析 <properties /> 标签
propertiesElement(root.evalNode("properties"));
// 解析 <settings /> 标签
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加载自定义的 VFS 实现类
loadCustomVfs(settings);
// 解析 <typeAliases /> 标签
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 <plugins /> 标签
pluginElement(root.evalNode("plugins"));
// 解析 <objectFactory /> 标签
objectFactoryElement(root.evalNode("objectFactory"));
// 解析 <objectWrapperFactory /> 标签
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析 <reflectorFactory /> 标签
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 赋值 <settings /> 到 Configuration 属性
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析 <environments /> 标签
environmentsElement(root.evalNode("environments"));
// 解析 <databaseIdProvider /> 标签
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析 <typeHandlers /> 标签
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 <mappers /> 标签
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 2.1.2 解析Mapper配置文件
回顾刚开 始介绍的加载配置文件的过程中,会对mybatis-config.xm l中的各个标签都进行解析,其中有mappers 标签用来引入mapper.xml文件或者配置mapper接口的目录:
<mappers>
<mapper resource="mapper/EmpMapper.xml"></mapper>
</mappers>
2
3
<mapper namespace="com.hukai.demo.dao.IEmpDao">
<select id="findAll" resultType="com.hukai.demo.pojo.Emp">
select * from emp
</select>
</mapper>
2
3
4
5
看看在 XMLConfigBuilder 中对于mappers标签是如何解析的:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历子节点
for (XNode child : parent.getChildren()) {
// 如果是 package 标签,则扫描该包
if ("package".equals(child.getName())) {
// 获得包名
String mapperPackage = child.getStringAttribute("name");
// 添加到 configuration 中
configuration.addMappers(mapperPackage);
// 如果是 mapper 标签,
} else {
// 获得 resource、url、class 属性
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 使用相对于类路径的资源引用
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 获得 resource 的 InputStream 对象
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建 XMLMapperBuilder 对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 执行解析
mapperParser.parse();
// 使用完全限定资源定位符(URL)
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
// 获得 url 的 InputStream 对象
InputStream inputStream = Resources.getUrlAsStream(url);
// 创建 XMLMapperBuilder 对象
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
// 执行解析
mapperParser.parse();
// 使用映射器接口实现类的完全限定类名
} else if (resource == null && url == null && mapperClass != null) {
// 获得 Mapper 接口
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 添加到 configuration 中
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
可以看出Mapper文件的具体解析是由XMLMapperBuilder完成的。
每一个select标签会在初始化配置文件时被解析封装成一个MappedStatement对象,然后存储在Configuration对象的mappedStatements属性中,mappedStatements 是一个HashMap,存储时key=全限定类名+方法名,value=对应的MappedStatement对象。
在configuration中对应的属性为:
Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>
# 2.2 执行SQL流程
第二步,通过 SqlSessionFactory 创建 SqlSession,SqlSession 是 MyBatis 暴露给外部使用的统一接口层,所有和数据库打交道的操作都通过 SqlSession 这层。
下面通过时序图描述 SqlSession 对象的创建流程:
# 2.2.1 SqlSession
SqlSession是MyBatis中用于和数据库交互的顶层类,完成必要数据库增删改查功能。通常将它与ThreadLocal绑定,一个会话使用一个SqlSession,并且在使用完毕后需要close。
SqlSession是一个接口,它有两个实现类:DefaultSqlSession (默认)和SqlSessionManager(弃用,不做介绍)。
DefaultSqlSession中的两个最重要的参数,configuration与初始化时的相同,Executor为执行器。
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
/**
* 是否自动提交事务
*/
private final boolean autoCommit;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
我们继续跟进selectList方法,看看是如何执行的:
//8.进入selectList方法,多个重载方法
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//调用Executor中的方法处理
//RowBounds是用来逻辑分页
//wrapCollection(parameter)是用来装饰集合或者数组参数
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
不难看出,SqlSession中的selectList方法只是根据传入的key值找到了Map中对应的MappedStatemen对象,然后交给Executor对象去执行jdbc操作。
# 2.2.2 Executor
Executor也是一个接口,其默认实现抽象类为BaseExecutor:
继续源码中的步骤,进入executor.query():
//此方法在SimpleExecutor的父类BaseExecutor中实现
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
BoundSql boundSql = ms.getBoundSql(parameter);
//为本次查询创建缓存的Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
// 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
// queryStack + 1
queryStack++;
// 从一级缓存中,获取查询结果
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
// 获取到,则进行处理
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
// 获得不到,则从数据库中查询
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// queryStack - 1
queryStack--;
}
if (queryStack == 0) {
// 执行延迟加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
// 清空 deferredLoads
deferredLoads.clear();
// 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
// 从数据库中读取操作
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 执行读操作
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 从缓存中,移除占位对象
localCache.removeObject(key);
}
// 添加到缓存中
localCache.putObject(key, list);
// 暂时忽略,存储过程相关
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
doQuery方法在具体实现类中定义,我们转到SimpleExecutor中:
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 传入参数创建StatementHanlder对象来执行查询
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建jdbc中的statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行 StatementHandler ,进行读操作
return handler.query(stmt, resultHandler);
} finally {
// 关闭 StatementHandler 对象
closeStatement(stmt);
}
}
// 初始化 StatementHandler 对象
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获得 Connection 对象
Connection connection = getConnection(statementLog);
// 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);
return stmt;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
上述的Executor.query()方法几经转折,最后会创建一个StatementHandler对象,然后将必要的参数传递给StatementHandler,使用StatementHandler来完成对占位符的赋值,数据库的查询,最终返回List结果集。
从上面的代码中我们可以看出,Executor的功能和作用是:
- 根据传递的参数,完成SQL语句的动态解析,生成BoundSql对象,供StatementHandler使用;
- 为查询创建缓存,以提高性能;
- 创建JDBC的Statement连接对象,传递给StatementHandler对象,返回List查询结果;
下面通过时序图描述Exector的执行流程,真实的调用链路类比较多,这里简化了调用链路,省略了一些装饰类、代理类,便于理解:

# 2.2.2 StatementHandler
StatementHandler同样是个接口,其默认实现抽象类为BaseStatementHandler:
BaseStatementHandler有两个重要的参数,ParameterHandler和ResultSetHandler,分别参数处理器以及结果集处理器,因此StatementHandler对象主要依靠这连个对象完成两个工作:
- 对于JDBC的PreparedStatement类型的对象,创建的过程中,我们使用的是SQL语句字符串会包含若干个?占位符,我们其后再对占位符进行设值。StatementHandler通过parameterize(statement)方法对 S tatement 进行设值;
- StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler)方法来完成执行Statement,和将Statement对象返回的resultSet封装成List;
进入到 StatementHandler的parameterize(statement)方法的实现:
@Override
public void parameterize(Statement statement) throws SQLException {
//使用ParameterHandler对象来完成对Statement的设值
parameterHandler.setParameters((PreparedStatement) statement);
}
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 遍历 ParameterMapping 数组
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
// 获得 ParameterMapping 对象
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
// 获得值
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 获得 typeHandler、jdbcType 属性
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
// 设置 ? 占位符的参数
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
从上述的代码可以看到,StatementHandler的parameterize(Statement)方法调用了ParameterHandler的setParameters(statement)方法。
ParameterHandler的setParameters(Statement )方法负责根据我们输入的参数,对statement对象的?占位符处进行赋值。
完成对占位符的赋值后,紧接着调用了StatementHandler的query方法,我们进入到进入到StatementHandler 的List query(Statement statement, ResultHandler resultHandler)方法的实现:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行查询
ps.execute();
// 处理返回结果
return resultSetHandler.handleResultSets(ps);
}
2
3
4
5
6
7
8
从上述代码我们可以看出,StatementHandler 的List query(Statement statement, ResultHandler resultHandler)方法的实现,是调用了 ResultSetHandler的handleResultSets(Statement)方法。
ResultSetHandler的handleResultSets(Statement)方法会将 Statement 语句执行后生成的 resultSet
结果集转换成List结果集。
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// 多 ResultSet 的结果集合,每个 ResultSet 对应一个 Object 对象。而实际上,每个 Object 是 List<Object> 对象。
// 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,multipleResults 最多就一个元素。
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 获得 ResultMap 数组
// 在不考虑存储过程的多 ResultSet 的情况,普通的查询,实际就一个 ResultSet ,也就是说,resultMaps 就一个元素。
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount); // 校验
while (rsw != null && resultMapCount > resultSetCount) {
// 获得 ResultMap 对象
ResultMap resultMap = resultMaps.get(resultSetCount);
// 处理 ResultSet ,将结果添加到 multipleResults 中
handleResultSet(rsw, resultMap, multipleResults, null);
// 获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
rsw = getNextResultSet(stmt);
// 清理
cleanUpAfterHandlingResultSet();
// resultSetCount ++
resultSetCount++;
}
// 因为 `mappedStatement.resultSets` 只在存储过程中使用,本系列暂时不考虑,忽略即可
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
// 如果是 multipleResults 单元素,则取首元素返回
return collapseSingleResultList(multipleResults);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 3. Mapper代理方式源码剖析
首先来回顾下Mapper代理方式的写法:
@Test
public void testFindById(){
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
sqlSession = sqlSessionFactory.openSession();
empMapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = empMapper.findById(7844);
System.out.println(emp);
sqlSession.close();
}
2
3
4
5
6
7
8
9
10
思考一个问题,通常的Mapper接口我们都没有实现的方法却可以使用,是为什么呢?
很明显必然是使用了动态代理,接下来,我们通过分析源码找到证据。
# 3.1 获取代理对象
进入sqlSession.getMapper(EmpMapper.class)方法:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
2
3
4
调用了Configuration对象的getMapper方法,继续跟进:
/**
* MapperRegistry 对象
*/
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
2
3
4
5
6
7
8
调用了MapperRegistry对象的getMapper方法,MapperRegistry 是 Configuration 的一个属性。

MapperRegistry 缓存了 MapperProxyFactory 的 Map 集合,内部的方法也几乎都是围绕这个map集合进行的。
我们知道Configuration对象封装了所有配置信息,MapperRegistry对象既然是它的一个属性,那必然也是在解析配置文件时生成的。
talk is cheap, show me the code:
// XMLConfigBuilder对象,mappers标签解析方法
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
// 遍历子节点
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 获得包名
String mapperPackage = child.getStringAttribute("name");
// 添加到 configuration 中
configuration.addMappers(mapperPackage);
// 如果是 mapper 标签,
} else {
...
}
}
}
}
// Configuration对象
public void addMappers(String packageName) {
// 扫描该包下所有的 Mapper 接口,并添加到 mapperRegistry 中
mapperRegistry.addMappers(packageName);
}
// MapperRegistry对象
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
/**
* 扫描指定包,并将符合的类,添加到 {@link #knownMappers} 中
*
* @since 3.2.2
*/
public void addMappers(String packageName, Class<?> superType) {
// 扫描指定包下的指定类
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
// 遍历,添加到 knownMappers 中
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
public <T> void addMapper(Class<T> type) {
// 判断,必须是接口。
if (type.isInterface()) {
// 已经添加过,则抛出 BindingException 异常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 添加到 knownMappers 中
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 解析 Mapper 的注解配置
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
// 标记加载完成
loadCompleted = true;
} finally {
// 若加载未完成,从 knownMappers 中移除
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
知道MapperProxyFactory如何产生的后,我们来看看MapperRegistry对象的getMapper方法又是如何返回代理对象的:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获得 MapperProxyFactory 对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 不存在,则抛出 BindingException 异常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
/// 通过动态代理工厂生成实例。
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
继续跟进MapperProxyFactory的newInstance方法:
//MapperProxyFactory类中的newInstance方法
public T newInstance(SqlSession sqlSession) {
// 创建了JDK动态代理的invocationHandler接口的实现类mapperProxy
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 调用了重载方法
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
2
3
4
5
6
7
8
9
10
11
bingo!看来返回的代理对象是通过JDK的动态代理实现的,通过参数判断MapperProxy对象必然是实现了java.lang.reflect.InvocationHandler接口。调用 Mapper 的任何方法都会执行 MapperProxy 的 invoke 方法。
为了便于理解,下面通过时序图描述Mapper对象的获取流程:

# 3.2 代理对象调用方法
我们已经知道了Mapper接口的的真实对象是MapperProxy,MapperProxy对象实现了java.lang.reflect.InvocationHandler接口,因此调用Mapper的任何方法都会执行MapperProxy 的invoke方法。接下来我们看看MapperProxy的invoke方法时如何定义的:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是 Object 定义的方法,直接调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 获得 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 重点在这:MapperMethod最终调用了执行的方法
return mapperMethod.execute(sqlSession, args);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
最终调用了MapperMethod对象的execute方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断mapper中的方法类型,最终调用的还是SqlSession中的方法
switch (command.getType()) {
case INSERT: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行 INSERT 操作
// 转换 rowCount
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 转换 rowCount
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 转换 rowCount
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// 无返回,并且有 ResultHandler 方法参数,则将查询的结果,提交给 ResultHandler 进行处理
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
// 执行查询,返回列表
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
// 执行查询,返回 Map
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
// 执行查询,返回 Cursor
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
// 执行查询,返回单个对象
} else {
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
// 查询单条
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional() &&
(result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
// 返回结果为 null ,并且返回类型为基本类型,则抛出 BindingException 异常
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
// 返回结果
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
最终调用的还是SqlSession中的方法,后续的流程就和传统模式是一致的了。

通过上面的分析,简单总结一下,我们可以抽象出 MyBatis 在执行一条SQL查询的过程中涉及到的主要类:Configuration、SqlSession、MapperProxy、Exector,根据这些类画出如下 MyBatis 执行 SQL 的时序图:
