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

执行器是如何创建的
首先祭出Mybatis的基本使用的代码
1 2 3 4 5 6 7 8 9 10 11
| String resource = "example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream); 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 {
final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
|
总结一下整个获取Executor的流程:
- 从封装了所有的配置信息中
Configuration获取包含连接数据库等信息的Environment对象。将Environment封装得到TransactionFactory。然后将Environment中的与连接数据库之间相关的dataSource(即我们在配置文件中dataSource节点下配置的那些信息)和事务隔离级别,是否自动提交三个信息封装,就得到了Transaction对象,Transaction对象中除了这些信息之外,还包含操作数据库的一些最最基本的操作。
- 有了
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); 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(); String sql = boundSql.getSql(); if (hasStatementFor(sql)) { stmt = getStatement(sql); applyTransactionTimeout(stmt); } else { 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
| private final List<Statement> statementList = new ArrayList<>(); private final List<BatchResult> batchResultList = new ArrayList<>(); private String currentSql; 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)) { int last = statementList.size() - 1; stmt = statementList.get(last); applyTransactionTimeout(stmt); handler.parameterize(stmt); BatchResult batchResult = batchResultList.get(last); batchResult.addParameterObject(parameterObject); } else { Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); currentSql = sql; currentStatement = ms; statementList.add(stmt); batchResultList.add(new BatchResult(ms, sql, parameterObject)); } 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方法其实可以理解为特殊的selectList。selectOne方法实际上也是调用的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 {
MappedStatement ms = configuration.getMappedStatement(statement);
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 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 { 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类中的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 = 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()) { 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; }
|
我们次这个方法中可以发现,开始还是会尝试取缓存中直接取缓存的数据,如果缓存中没有取到数据,那么就会调用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()); 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 ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps); }
|