一张图了解Mybatis执行器

- SqlSession内部维护了一个Executor类,这里是CachingExecutor,所以SqlSession内部会调用CachingExecutor的query()方法执行查询流程
- CachingExecutor内部有一个被声明为Executor类的delegate属性,在完成内部的二级缓存操作后,它会调用BaseExecutor的query()方法
- BaseExecutor会做一些公共的事情,比如一级缓存的处理,获取链接等等,它也不会真的执行查询数据的操作,而是调用子类的doQuery()、doUpdate()去执行数据库查询操作
- 子类执行数据库查询逻辑,子类有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; private final Executor executor; 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 { 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); } 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(); } deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { 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; }
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)
|