一张图了解Mybatis执行器

Mybatis执行器流程-1

  1. SqlSession内部维护了一个Executor类,这里是CachingExecutor,所以SqlSession内部会调用CachingExecutor的query()方法执行查询流程
  2. CachingExecutor内部有一个被声明为Executor类的delegate属性,在完成内部的二级缓存操作后,它会调用BaseExecutor的query()方法
  3. BaseExecutor会做一些公共的事情,比如一级缓存的处理,获取链接等等,它也不会真的执行查询数据的操作,而是调用子类的doQuery()、doUpdate()去执行数据库查询操作
  4. 子类执行数据库查询逻辑,子类有SimpleExecutor、ReuseExecutor、BatchExecutor,还有一个ClosedExecutor,由于它和前面三个做的事情完全不同,所以不再解释它。

SqlSession

默认情况下是DefaultSqlSession,操作数据动作是调用的CachingExecutor.query()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DefaultSqlSession implements SqlSession {

private final Configuration configuration;
// 内部维护了一个executor属性,CachingExecutor
private final Executor executor;
// 省略代码

// 具体的查询逻辑,可以看到是调用的CachingExecutor.query()方法
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
dirty |= ms.isDirtySelect();
// 查询动作
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}

CachingExecutor

主要是处理二级缓存。缓存未开启或开启了但缓存中没值,则执行查库动作。

1
2
3
4
public class CachingExecutor implements Executor {
// 维护了一个delegate属性
private final Executor delegate;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
// 如果二级缓存开启,就处理二级缓存逻辑
if (cache != null) {
flushCacheIfRequired(ms);
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;
}
}
// 二级缓存未开启,查库
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

BaseExecutor

处理公共逻辑,然后调用子类的方法完成与数据库的交互。

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()) {
clearLocalCache();
}
List<E> list;
try {
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--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

与数据库交互并维护一级缓存

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;
}
// doQuery是一个抽象方法,由子类实现
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

下面我们具体分析下三个子类的区别,这里我们写一个测试类,分别调试下三个子类的行为。

在测试类中添加以下属性:

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
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.*;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.*;
import org.apache.ibatis.transaction.jdbc.JdbcTransaction;
import org.junit.Before;
import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
@Slf4j
public class UserMybatisTest {
private Configuration configuration;
private JdbcTransaction jdbcTransaction;
private SqlSessionFactory sqlSessionFactory;

@Before
public void init() throws SQLException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(UserMapper.class.getResourceAsStream("/mybatis-config.xml"));
configuration = sqlSessionFactory.getConfiguration();
Connection connection = DriverManager.getConnection(JDBCConstant.URL, JDBCConstant.USERNAME, JDBCConstant.PASSWORD);
jdbcTransaction = new JdbcTransaction(connection);
}
}

SimpleExecutor

简单执行器,每次都会创建一个新的预处理器

1
2
3
4
5
6
7
8
9
10
@Test
public void testSelectUserById() throws SQLException {
SimpleExecutor simpleExecutor = new SimpleExecutor(configuration, jdbcTransaction);
MappedStatement mappedStatement = configuration.getMappedStatement("org.example.mybatis_reader.mybatis.UserMapper.selectUserById");
simpleExecutor.doQuery(mappedStatement, 1L, RowBounds.DEFAULT,
SimpleExecutor.NO_RESULT_HANDLER, mappedStatement.getBoundSql(10L));

simpleExecutor.doQuery(mappedStatement, 1L, RowBounds.DEFAULT,
SimpleExecutor.NO_RESULT_HANDLER, mappedStatement.getBoundSql(10L));
}

看下执行结果,有两个Preparing,说明执行了两次预处理。

1
2
3
4
5
6
[main] DEBUG o.e.m.m.UserMapper.selectUserById - ==>  Preparing: SELECT * FROM T_USER WHERE id = ?
[main] DEBUG o.e.m.m.UserMapper.selectUserById - ==> Parameters: 1(Long)
[main] DEBUG o.e.m.m.UserMapper.selectUserById - <== Total: 1
[main] DEBUG o.e.m.m.UserMapper.selectUserById - ==> Preparing: SELECT * FROM T_USER WHERE id = ?
[main] DEBUG o.e.m.m.UserMapper.selectUserById - ==> Parameters: 1(Long)
[main] DEBUG o.e.m.m.UserMapper.selectUserById - <== Total: 1

ReuseExecutor

在SimpleExecutor中每次都会创建一个新的预处理器,这无疑是对性能的浪费,所以ReuseExecutor对它进行了优化,相同SQL只进行一次预处理即可。

1
2
3
4
5
6
7
8
9
10
@Test
public void testReuseExecutor() throws SQLException {
ReuseExecutor reuseExecutor = new ReuseExecutor(configuration, jdbcTransaction);
MappedStatement mappedStatement = configuration.getMappedStatement("org.example.mybatis_reader.mybatis.UserMapper.selectUserById");
reuseExecutor.doQuery(mappedStatement, 1L, RowBounds.DEFAULT,
SimpleExecutor.NO_RESULT_HANDLER, mappedStatement.getBoundSql(10L));

reuseExecutor.doQuery(mappedStatement, 1L, RowBounds.DEFAULT,
SimpleExecutor.NO_RESULT_HANDLER, mappedStatement.getBoundSql(10L));
}

从执行结果中可以看到只进行了一次预处理

1
2
3
4
5
[main] DEBUG o.e.m.m.UserMapper.selectUserById - ==>  Preparing: SELECT * FROM T_USER WHERE id = ?
[main] DEBUG o.e.m.m.UserMapper.selectUserById - ==> Parameters: 1(Long)
[main] DEBUG o.e.m.m.UserMapper.selectUserById - <== Total: 1
[main] DEBUG o.e.m.m.UserMapper.selectUserById - ==> Parameters: 1(Long)
[main] DEBUG o.e.m.m.UserMapper.selectUserById - <== Total: 1

BatchExecutor

批处理器,顾名思义就是将多个SQL语句一次性发送到数据库服务器,然后一次性执行,以提高性能。要注意的是只有修改操作才可以使用批处理器,而且使用时必须执行flushStatements。

1
2
3
4
5
6
7
8
9
10
11
@Test
public void testBatchExecutor() throws SQLException {
BatchExecutor batchExecutor = new BatchExecutor(configuration, jdbcTransaction);
MappedStatement mappedStatement = configuration.getMappedStatement("org.example.mybatis_reader.mybatis.UserMapper.updateUserNameById");
HashMap<String, Object> param = new HashMap<>();
param.put("arg0", "张三改名");
param.put("arg1", 1L);
batchExecutor.doUpdate(mappedStatement, param);
batchExecutor.doUpdate(mappedStatement, param);
batchExecutor.flushStatements(Boolean.FALSE);
}

执行结果,可以看到不是执行了两次,而是执行了一次预处理,然后统一提交给了数据库做变更。

1
2
3
[main] DEBUG o.e.m.m.U.updateUserNameById - ==>  Preparing: UPDATE T_USER SET USER_NAME = ? WHERE id = ?
[main] DEBUG o.e.m.m.U.updateUserNameById - ==> Parameters: 张三改名(String), 1(Long)
[main] DEBUG o.e.m.m.U.updateUserNameById - ==> Parameters: 张三改名(String), 1(Long)