MyBatis 架构与原理到执行流程
Mybatis 经过一系列操作最终封装成
Statement\PreparedStatement
,交个 JDBC 处理,所以我们需要回归下 JDBC。参考JDBC 简单的示例 (opens new window)
# MyBatis 功能架构设计
# MyBatis 功能架构讲解
我们把 Mybatis 的功能架构分为三层:
(1)API 接口层:提供给外部使用的接口 API,开发人员通过这些本地 API 来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
(2)数据处理层:负责具体的 SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
(3)基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
# MyBatis 框架架构
# MyBatis 框架架构讲解:
这张图从上往下看。MyBatis 的初始化,会从 mybatis-config.xml 配置文件,解析构造成 Configuration 这个类,就是图中的红框。
(1)加载配置:配置来源于两个地方,一处是配置文件,一处是 Java 代码的注解,将 SQL 的配置信息加载成为一个个 MappedStatement 对象(包括了传入参数映射配置、执行的 SQL 语句、结果映射配置),存储在内存中。
(2)SQL 解析:当 API 接口层接收到调用请求时,会接收到传入 SQL 的 ID 和传入对象(可以是 Map、JavaBean 或者基本数据类型),Mybatis 会根据 SQL 的 ID 找到对应的 MappedStatement,然后根据传入参数对象对 MappedStatement 进行解析,解析后可以得到最终要执行的 SQL 语句和参数。
(3)SQL 执行:将最终得到的 SQL 和参数拿到数据库进行执行,得到操作数据库的结果。
(4)结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成 HashMap、JavaBean 或者基本数据类型,并将最终结果返回。
# MyBatis 核心类
# 1、SqlSessionFactoryBuilder
每一个 MyBatis 的应用程序的入口是 SqlSessionFactoryBuilder。
它的作用是通过 XML 配置文件创建 Configuration 对象(当然也可以在程序中自行创建),然后通过 build 方法创建 SqlSessionFactory 对象。没有必要每次访问 Mybatis 就创建一次 SqlSessionFactoryBuilder,通常的做法是创建一个全局的对象就可以了。示例程序如下:
private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private static SqlSessionFactory sqlSessionFactory;
private static void init() throws IOException {
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
}
org.apache.ibatis.session.Configuration 是mybatis初始化的核心。
2
3
4
5
6
7
8
9
10
mybatis-config.xml 中的配置,最后会解析 xml 成 Configuration 这个类。
SqlSessionFactoryBuilder 根据传入的数据流(XML)生成 Configuration 对象,然后根据 Configuration 对象创建默认的 SqlSessionFactory 实例。
# 2、SqlSessionFactory 对象由 SqlSessionFactoryBuilder 创建:
它的主要功能是创建 SqlSession 对象,和 SqlSessionFactoryBuilder 对象一样,没有必要每次访问 Mybatis 就创建一次 SqlSessionFactory,通常的做法是创建一个全局的对象就可以了。SqlSessionFactory 对象一个必要的属性是 Configuration 对象,它是保存 Mybatis 全局配置的一个配置对象,通常由 SqlSessionFactoryBuilder 从 XML 配置文件创建。这里给出一个简单的示例:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC
"-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 配置别名 -->
<typeAliases>
<typeAlias type="org.iMybatis.abc.dao.UserDao" alias="UserDao" />
<typeAlias type="org.iMybatis.abc.dto.UserDto" alias="UserDto" />
</typeAliases>
<!-- 配置环境变量 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/iMybatis?characterEncoding=GBK" />
<property name="username" value="iMybatis" />
<property name="password" value="iMybatis" />
</dataSource>
</environment>
</environments>
<!-- 配置mappers -->
<mappers>
<mapper resource="org/iMybatis/abc/dao/UserDao.xml" />
</mappers>
</configuration>
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
# Configuration
MyBatis 在初始化的时候,会将 MyBatis 的配置信息全部加载到内存中,使用 org.apache.ibatis.session.Configuration 实例来维护
下⾯进入对配置文件解析部分: ⾸先对 Configuration 对象进行介绍:
Configuration 对象的结构和 xml 配置文件的对象几乎相同。 回顾⼀下 xml 中的配置标签有哪些: properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理器),objectFactory (对象工⼚),mappers (映射器)等 Configuration 也有对应的对象属性来封装它们也就是说,初始化配置文件信息的本质就是创建 Configuration 对象,将解析的 xml 数据封装到 Configuration 内部属性中
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
---
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.
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 解析 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
# 介绍⼀下 MappedStatement :
作用:MappedStatement 与 Mapper 配置文件中的⼀个 select/update/insert/delete 节点相对应。 mapper 中配置的标签都被封装到了此对象中,主要用途是描述⼀条 SQL 语句。
初始化过程: 回顾刚开始介绍的加载配置文件的过程中,会对 mybatis-config.xml 中的各个标签都进行解析,其中有 mappers 标签用来引入 mapper.xml 文件或者配置 mapper 接口的⽬录
<select id="getUser" resultType="user" >
select * from user where id=#{id}
</select>
2
3
这样的⼀个 select 标签会在初始化配置文件时被解析封装成⼀个 MappedStatement 对象,然后存储在 Configuration 对象的 mappedStatements 属性中,mappedStatements 是⼀个 HashMap,存储时 key =全限定类名+方法名,value =对应的 MappedStatement 对象
在 configuration中对应的属性为
Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>
("Mapped Statements collection")
2
在 XMLConfigBuilder 中的处理:
private void parseConfiguration(XNode root) {
try {
//省略其他标签的处理
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
到此对 xml 配置文件的解析就结束了,回到步骤 2.中调用的重载 build 方法
// 5.调用的重载方法
public SqlSessionFactory build(Configuration config) {
//创建了 DefaultSqlSessionFactory 对象,传入 Configuration 对象。
return new DefaultSqlSessionFactory(config);
}
2
3
4
5
# 3、SqlSession
SqlSession 是⼀个接口,它类似于数据库的 session 概念,由于不是线程安全的。通常将它与 ThreadLocal 绑定,⼀个会话使用⼀个 SqlSession,并且在使用完毕后需要 close。它有两个实现类:DefaultSqlSession (默认)和 SqlSessionManager (弃用,不做介绍) SqlSession 是 MyBatis 中用于和数据库交互的顶层类,
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
/**
* 是否自动提交事务
*/
private final boolean autoCommit;
/**
* 是否发生数据变更
*/
private boolean dirty;
/**
* Cursor 数组
*/
private List<Cursor<?>> cursorList;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SqlSession 中的两个最重要的参数,configuration 与初始化时的相同,Executor 为执行器
SqlSession :默认创建 DefaultSqlSession 并且开启一级缓存,创建执行器 、赋值。
SqlSession 有一个重要的方法 getMapper,顾名思义,这个方式是用来获取 Mapper 对象的。什么是 Mapper 对象?根据 Mybatis 的官方手册,应用程序除了要初始并启动 Mybatis 之外,还需要定义一些接口,接口里定义访问数据库的方法,存放接口的包路径下需要放置同名的 XML 配置文件。
SqlSession 的 getMapper 方法是联系应用程序和 Mybatis 纽带,应用程序访问 getMapper 时,Mybatis 会根据传入的接口类型和对应的 XML 配置文件生成一个代理对象,这个代理对象就叫 Mapper 对象。应用程序获得 Mapper 对象后,就应该通过这个 Mapper 对象来访问 Mybatis 的 SqlSession 对象,这样就达到里插入到 Mybatis 流程的目的。
SqlSession session= sqlSessionFactory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
UserDto user = new UserDto();
user.setUsername("iMybatis");
List<UserDto> users = userDao.queryUsers(user);
---
public interface UserDao {
public List<UserDto> queryUsers(UserDto user) throws Exception;
}
---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.iMybatis.abc.dao.UserDao">
<select id="queryUsers" parameterType="UserDto" resultType="UserDto"
useCache="false">
<![CDATA[
select * from t_user t where t.username = #{username}
]]>
</select>
</mapper>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 4、Executor
Executor 对象在创建 Configuration 对象的时候创建,并且缓存在 Configuration 对象里。Executor 对象的主要功能是调用 StatementHandler 访问数据库,并将查询结果存入缓存中(如果配置了缓存的话)。
Executor 也是⼀个接口,他有三个常用的实现类:
BatchExecutor (重用语句并执行批量更新)
ReuseExecutor (重用预处理语句 prepared statements)
SimpleExecutor (普通的执行器,默认)
继续分析,初始化完毕后,我们就要执行 SQL 了
SqlSession sqlSession = factory.openSession(); List<User> list = sqlSession.selectList("com.admin4j.mapper.UserMapper.getUserByName");
1
2
# 获得 sqlSession
//6. 进入openSession方法
@Override
public SqlSession openSession() {
//getDefaultExecutorType()传递的是SimpleExecutor
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
---
//7. 进入openSessionFromDataSource。
//ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
//openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获得 Environment 对象
final Environment environment = configuration.getEnvironment();
// 创建 Transaction 对象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建 Executor 对象
final Executor executor = configuration.newExecutor(tx, execType);
// 创建 DefaultSqlSession 对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
// 如果发生异常,则关闭 Transaction 对象
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. 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
27
28
29
30
31
# 执行 sqlsession 中的 api,以 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 {
// 获得 MappedStatement 对象
// 根据传入的全限定名+方法名从映射的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
27
28
# 源码剖析-executor
再创建 SqlSeeeion 时会创建 executor
// 创建 Executor 对象
final Executor executor = configuration.newExecutor(tx, execType);
2
再 Configuration 类中创建 Executor 对象源码,其中如果开启缓存的换就将创建的 executor 设为 CachingExecutor 的代理,executor = new CachingExecutor(executor)
,并返回 CachingExecutor
/**
* 创建 Executor 对象
*
* @param transaction 事务对象
* @param executorType 执行器类型
* @return Executor 对象
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 获得执行器类型
executorType = executorType == null ? defaultExecutorType : executorType; // 使用默认
executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 使用 ExecutorType.SIMPLE
// 创建对应实现的 Executor 对象
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 如果开启缓存,创建 CachingExecutor 对象,进行包装
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 应用插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
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()
//此方法在SimpleExecutor的⽗类BaseExecutor中实现
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 为本次查询创建缓存的Key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 查询
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
---
//进入query的重载方法中
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 从 MappedStatement 中获取 Cache,注意这里的 Cache 是从MappedStatement中获取的
// 也就是我们上面解析Mapper中<cache/>标签中创建的,它保存在Configration中
// 我们在初始化解析xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
Cache cache = ms.getCache();
// 如果配置文件中没有配置 <cache>,则 cache 为空
if (cache != null) {
//如果需要刷新缓存的话就刷新:flushCache="true"
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
// 暂时忽略,存储过程相关
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 从二级缓存中,获取结果
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 如果没有值,则执行查询,这个查询实际也是先走一级缓存查询,一级缓存也没有的话,则进行DB查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 缓存查询结果
tcm.putObject(cache, key, list); // issue #578 and #116
}
// 如果存在,则直接返回结果
return list;
}
}
// 不使用缓存,则从数据库中查询(会查一级缓存) 调用父类的方法
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
--- 父类BaseExecutor
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;
}
--- 最终调用子类的doQuery 实现具体查询
@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);
}
}
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
上述的 Executor.query()方法几经转折,最后会创建⼀个 StatementHandler 对象,然后将必要的参数传 递给 StatementHandler,使用 StatementHandler 来完成对数据库的查询,最终返回 List 结果集。 从上⾯的代码中我们可以看出,Executor 的功能和作用是
- 1、根据传递的参数,完成 SQL 语句的动态解析,⽣成 BoundSql 对象,供 StatementHandler 使用;
- 2、为查询创建缓存,以提⾼性能
- 3、创建 JDBC 的Statement(PreparedStatement)连接对象,传递给StatementHandler对象,返回 List 查询结果。
# 5、StatementHandler
StatementHandler 对象主要完成两个工作:
- 对于 JDBC 的 PreparedStatement 类型的对象,创建的过程中,我们使用的是 SQL 语句字符串会包含若干个?占位符,我们其后再对占位符进行设值。StatementHandler 通过 parameterize(statement)方法对 Statement 进行设值;
- StatementHandler 通过 List query(Statement statement, ResultHandler resultHandler)方法来完成执行 Statement,和将 Statement 对象返回的 resultSet 封装成 List; 进入到 StatementHandler 的 parameterize(statement)方法的实现:
上方 SimpleExecutor.doQuery() 方法初始化 StatementHandler 和 Statement
// 传入参数创建StatementHanlder对象来执行查询
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建jdbc中的statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行 StatementHandler ,进行读操作
return handler.query(stmt, resultHandler);
---
// 初始化 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
进入到 StatementHandler(PreparedStatementHandler) 的 parameterize(statement)方法的实现:
StatementHandler 包含各种 sql 参数 通过
parameterHandler.setParameters((PreparedStatement) statement);
构建出一个 PreparedStatement 对象
public void parameterize(Statement statement) throws SQLException {
//使用ParameterHandler对象来完成对Statement的设值
parameterHandler.setParameters((PreparedStatement) statement);
}
---
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
从上述的代码可以看到,StatementHandler 的 parameterize(Statement)方法调用了 ParameterHandler 的 setParameters(statement)方法, ParameterHandler 的 setParameters(Statement )方法负责根据我们输入的参数,对 statement 对象的 ?占位符处进行赋值。 进入到 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 结果集
# 6、ResultSetHandler
处理查询结果。
//
// HANDLE RESULT SETS
//
// 处理 {@link java.sql.ResultSet} 结果集
@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);
}
---
// 处理 ResultSet ,将结果添加到 multipleResults 中
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
// 暂时忽略,因为只有存储过程的情况,调用该方法,parentMapping 为非空
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
// 如果没有自定义的 resultHandler ,则创建默认的 DefaultResultHandler 对象
if (resultHandler == null) {
// 创建 DefaultResultHandler 对象
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
// 处理 ResultSet 返回的每一行 Row
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
// 添加 defaultResultHandler 的处理的结果,到 multipleResults 中
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 处理 ResultSet 返回的每一行 Row
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
// 关闭 ResultSet 对象
closeResultSet(rsw.getResultSet());
}
}
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
78
79
80
81
# Mapper 代理方式:
回顾下写法:
public static void main(String[] args) {
//前三步都相同
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
//这⾥不再调用SqlSession的api,⽽是获得了接口对象,调用接口中的方法。
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.getUserByName("tom");
}
2
3
4
5
6
7
8
9
思考⼀个问题,通常的 Mapper 接口我们都没有实现的方法却可以使用,是为什么呢?答案很简单动态代理
<mappers>
<mapper class="com.admin4j.mapper.UserMapper"/>
<package name="com.admin4j.mapper"/>
</mappers>
2
3
4
5
- 当解析 mappers 标签时,它会判断解析到的是 mapper 配置文件时,会再将对应配置文件中的增删改查标签封装成 MappedStatement 对象,存入 mappedStatements 中。(上文介绍了)当判断解析到接口时,会建此接口对应的 MapperProxyFactory 对象,存入 HashMap 中,key =接口的字节码对象,value =此接口对应的 MapperProxyFactory 对象
# 源码剖析-getMapper()
进入 sqlSession.getMapper(UserMapper.class )中
//DefaultSqlSession 中的 getMapper
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
---
//configuration 中的给 getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
---
//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);
}
}
---
//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);
}
---
// 构造,传入了SqlSession,说明每个session中的代理对象的不同的!
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
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
# 源码剖析-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);
}
进入 execute 方法进入 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 创建过程总结
- 首先
SqlSessionFactoryBuilder
去读取 mybatis 的配置文件,然后 build 一个DefaultSqlSessionFactory
,即得到SqlSessionFactory
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//然后返回一个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
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
根据 xml 或者注解生成 MappedStatement 对象放在 Configurationd 对象里属性为 mappedStatements 的一个 HashMap,存储时 key =全限定类名+方法名,value =对应的 MappedStatement 对象
/**
* MappedStatement 映射
*
* KEY:`${namespace}.${id}`
*/
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
2
3
4
5
6
- Configuration 配置类包含大量 new 方法创建各种类
每创建一个新的对象都会应用插件生成一个代理对象。以 Executor 为例,
/**
* 创建 Executor 对象
*
* @param transaction 事务对象
* @param executorType 执行器类型
* @return Executor 对象
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 获得执行器类型
executorType = executorType == null ? defaultExecutorType : executorType; // 使用默认
executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 使用 ExecutorType.SIMPLE
// 创建对应实现的 Executor 对象
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 如果开启缓存,创建 CachingExecutor 对象,进行包装
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 应用插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
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
获取到
SqlSessionFactory
之后,就可以利用SqlSessionFactory
方法的openSession
来获取SqlSession
对象了。得到
SqlSession
对象之后就可以利用SqlSession
内部的方法进行 CRUD 操作了。注意一点,
Connection
对象是在SqlSession
对象创建之后进行 CURD 操作中创建的。深入查找之后找到在ManagedTransaction
类中找到获取Connection
对象的关键代码如下:
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
//dataSource 来源有三种,JndiDatasource,PooledDataSource,UnpooledDataSource,配置文件中定义
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
}
2
3
4
5
6
7
8
9
10
11
SqlSession 每执行完一个 sql 就会关闭链接(放入连接池)。如果不想让他关闭,可以参考MyBatis 千万级数据查询解决方案,避免 OOM (opens new window)
# 执行 sql 流程
- 调用 SqlSession 的 Executor 执行 sql
根据 ID 获取 MappedStatement,
MappedStatement ms = configuration.getMappedStatement(statement);
1
- Executor 调用 StatementHandler 创建 JDBC 的 Statement(PreparedStatement)连接对象,并且执行 sql 获取到 ResultSetHandler
- ResultSetHandler 装换成返回的具体对象
# MyBatis 成员层次&职责
- SqlSession 作为 MyBatis 工作的主要顶层 API,表示和数据库交互的会话,完成必要数据库增删改查功能
- Executor MyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护
- StatementHandler 封装了 JDBC Statement 操作,负责对 JDBCstatement 的操作,如设置参数、将 Statement 结果集转换成 List 集合。
- ParameterHandler 负责对用户传递的参数转换成 JDBC Statement 所需要的参数
- ResultSetHandler *负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合;
- TypeHandler 负责 java 数据类型和 jdbc 数据类型之间的映射和转换
- MappedStatement MappedStatement 维护了一条<select|update|delete|insert>节点的封
- SqlSource 负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封装到 BoundSql 对象中,并返回
- BoundSql 表示动态生成的 SQL 语句以及相应的参数信息
- Configuration MyBatis 所有的配置信息都维持在 Configuration 对象之中
# 参考
https://blog.csdn.net/zwx900102/article/details/108455514
mybatis 最常用的 SqlSessionFactory 和 SqlSession,你真的了解吗? (opens new window)
- 01
- idea 热部署插件 JRebel 安装及破解,不生效问题解决04-10
- 02
- spark中代码的执行位置(Driver or Executer)12-12
- 03
- 大数据技术之 SparkStreaming12-12