概述

执行器是Mybatis的核心接口之一,接口层提供的相关数据库操作都是基于Executor的子类实现的。
19CPiQ.png

执行器是如何创建的

首先祭出Mybatis的基本使用的代码

1
2
3
4
5
6
7
8
9
10
11
String resource = "example/mybatis-config.xml";
// 加载配置文件 并构建SqlSessionFactory对象
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 从SqlSessionFactory对象中获取 SqlSession对象
SqlSession sqlSession = factory.openSession();
// 执行操作
User user=new User();
user.setId(1);

User u= (User)sqlSession.selectOne("getUser", user);

在mybatis启动的时候,我们需要通过SqlSessionFactoryBuilder对象,调用其build方法来获取SqlSessionFactory对象。通过SqlSessionFactory对象的openSession方法来获取SqlSession对象。实际上执行器的创建就是在调用openSession方法的时候创建的。

通过源代码可以发现openSession方法背后实际调用的是openSessionFromDataSource这个方法。其具体实现如下:

1
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
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
/*从包含所有配置信息的configuration中获取Environment对象
Environment中包含了连接数据库的所有信息,包括driver,url,username,passowrd,TransactionFactory等众多的与数据库连接相关的信息
*/
final Environment environment = configuration.getEnvironment();

/**这段代码的作用就是简单的从Environment中取出TransactionFactory对象*/
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
/*调用TransactionFactory的newTransaction获取Transaction对象,Transaction对象其实也非常的简单,它是对数据库连接Connection,DataSource,TransactionIsolationLevel(事务隔离级别),autoCommit(是否自动提交)的封装,内部还有利用connection提交事务,回滚事务等一系列方法。拿到了Transaction对象,就拥有了最基础的数据库操作途径*/
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
/*newExecutor方法会根据执行器的类型(execType)选择创建不同的执行器,
newExecutor内部就是简单的判断
*/
final Executor executor = configuration.newExecutor(tx, execType);

/*拿到用于操作数据库的执行器,包含所有配置信息的Configuration对象等信息后,就可以将这些信息封装成为SqlSession
*/
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

总结一下整个获取Executor的流程:

  1. 从封装了所有的配置信息中Configuration获取包含连接数据库等信息的Environment对象。将Environment封装得到TransactionFactory。然后将Environment中的与连接数据库之间相关的dataSource(即我们在配置文件中dataSource节点下配置的那些信息)和事务隔离级别,是否自动提交三个信息封装,就得到了Transaction对象,Transaction对象中除了这些信息之外,还包含操作数据库的一些最最基本的操作。
  2. 有了Transaction和执行器类型,就可以通过newExecutor方法创建Executor.newExecutor方法内部就会根据执行器类别的不同,创建不同的执行器。

三大执行器在实现上有什么不同

简单执行器SimpleExecutor

Mybatis默认情况下是使用SimpleExecutor的。

查询方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

//每次执行都会调用prepareStatement创建新的Statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}

我们可以发现SimpleExecutor的一大特点在于其每次执行查询的时候,都会创建一个新的Statement对象。

复用执行器ReuseExecutor

ReuseExecutor之中有且仅有一个属性:
private final Map<String, Statement> statementMap = new HashMap<>();
它的作用,就是缓存Statement,以便复用Statement.
我们通过其doQuery方法来查看其到底是如何进行Statement复用的。

1
2
3
4
5
6
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
}

这个方法的实现看似和SimpleExecutor类似,其实玄机在于prepareStatement的实现。具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
//这个地方得到的sql已经是可以直接到数据库中查询的sql了
String sql = boundSql.getSql();
if (hasStatementFor(sql)) { //从缓存statement的map中查找是否有缓存
stmt = getStatement(sql);//直接从缓存中获取statement
//为Transaction设置一个过期时间
applyTransactionTimeout(stmt);
} else {
//没有缓存则获取连接,创建Statement
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}

从源码中我们可以看出ReuseStatement之所以称为Reuse是因为,其内部拥有一个缓存Statement的map,其中缓存键为待执行的sql。

批量执行器BatchExecutor

BatchExecutor内部的属性稍微的多一些;

1
2
3
4
5
6
7
8
//存储匹配的Statement
private final List<Statement> statementList = new ArrayList<>();
//存储执行的结果
private final List<BatchResult> batchResultList = new ArrayList<>();
//当前正在执行的sql
private String currentSql;
//当前正在使用的Statement
private MappedStatement currentStatement;

我们通过doUpdate方法,来分析其特点。

1
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
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
//如果当前要执行的sql与之前执行的sql相同,则复用statement
//注意此时currentSql还未更新,所以currentSql实际是上次执行的sql
int last = statementList.size() - 1;
//获取最后一个statement,复用statement
stmt = statementList.get(last);
//设置超时时间
applyTransactionTimeout(stmt);
//设置参数
handler.parameterize(stmt);//fix Issues 322
//获取批量执行结果对象
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
//创建新的statement对象
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt); //fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
//执行jdbc批量添加sql,并执行
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}

通过源代码我们也可以发现,其实BatchExecute内部也可以“复用”statement,前提是当前执行的sql和之前的一致。最后将sql交由jdbc去批量执行。

mybatis是如何利用执行器取操作数据的

我们以默认的SimpleExecutor执行器为例,来观察mybatis到底是如何执行sql的。

我们从这行代码出发,观察背后的执行逻辑:
User u= (User)sqlSession.selectOne("getUser", user);

selectOne方法其实可以理解为特殊的selectListselectOne方法实际上也是调用的selectList方法,然后从返回的list中取第一条数据即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {


/*这里的statement参数的值就是getUser,getMappedStatement方法就是简单的从configuration对象中的一个存储Statement的map中取出对应的MappedStatement。这个MappedStatement对象中,存储了Mapeper.xml中的信息,和对应接口的全路径*/
MappedStatement ms = configuration.getMappedStatement(statement);

/*调用executor的query方法之前,还需要对参数进行简单的包装,如果参数是list或array或collection就会放到map中,进行说明(标识参数的类型),简单类型就不做任何的处理。然后调用query方法。query方法到底做了什么处理,可以看query的具体实现:
*/

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();
}
}

query方法的具体实现如下:

1
2
3
4
5
6
7
8
9
10
11
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
/*这里的BoundSql包括sql(参数替换为?,还不能直接执行),parameterMappings,parameterObject,additionalParameters,metaParameters等数据*/
BoundSql boundSql = ms.getBoundSql(parameterObject);
/*计算缓存键*/
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);

/*调用了另一个重载方法*/
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

query的重载方法如下;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//尝试从MappedStatement中获取缓存Cache
Cache cache = ms.getCache();
if (cache != null) {//缓存不为为空

/*这个方法起清除了CachingExecutor中的TransactionalCacheManager中的Cache*/
flushCacheIfRequired(ms);
//如果MappedStatement开启缓存的,且结果处理器为空
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//从缓存中获取结果
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {//结果为空,就会重新去数据库中查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}

/*去数据库中查询数据的重担就落到delegate.query*/
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

最终来到了BaseExecutor类中的query方法了。

1
2
3
4
5
6
7
8
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取boundSql
BoundSql boundSql = ms.getBoundSql(parameter);
//计算缓存键
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

还是调用了另一个重载的query方法

1
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
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());
if (closed) {//确保执行器此时是开启状态
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
//如果queryStatck==0且需要清理cache就刷新Cache
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//如果结果处理器为null,那么还是尝试去缓存中取一取
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--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

我们次这个方法中可以发现,开始还是会尝试取缓存中直接取缓存的数据,如果缓存中没有取到数据,那么就会调用queryFromDatabase方法去数据库中查询。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//标记当前查询正在进行,对缓存进行占位
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;
}

这个方法又将查询的任务委派给了SimpleExecutor类下的doQuery方法

1
2
3
4
5
6
7
8
9
10
11
12
13

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();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//查询最终又落到了heanler的query方法上
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
1
2
3
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}

这个方法最后来到了PreparedStatementHandler类下的query方法

1
2
3
4
5
6
7
8
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//获取PreparedStatement方法,jdbc的预编译执行就是使用的PareparedStatement
PreparedStatement ps = (PreparedStatement) statement;
//这里其实就是jdbc方法了,从数据库中查询了
ps.execute();
return resultSetHandler.handleResultSets(ps);
}