ToB企服应用市场:ToB评测及商务社交产业平台

标题: Mybatis执行器 [打印本页]

作者: 怀念夏天    时间: 2024-7-3 18:01
标题: Mybatis执行器
mybatis执行sql语句的操作是由执行器(Executor)完成的,mybatis中一共提供了3种Executor:
类型名称功能REUSE重用执行器缓存PreparedStatement,下一次执行相同的sql可重用BATCH批量执行器将修改操作记录在本地,等待程序触发或有下一次查询时才批量执行修改操作SIMPLE简朴执行器对每一次执行都生成PreparedStatement,执行完就关闭,不缓存另外,mybatis 还提供了一个缓存执行器CachingExecutor,该执行器实际上是以上三种执行器的装饰类,用以处理缓存相关操作,实际干活的还是以上三种执行器之一。
Executor的继续结构如下:

1. BaseExecutor

BaseExecutor实现了Executor的基本操作,如:
接下来我们关注Executor的实现时,只关注留待子类实现的方法。
2. SimpleExecutor

SimpleExecutor会对每一次执行都生成PreparedStatement,执行完就关闭,不缓存,我们来看看它是怎么实现的,来看看它的doQuery(...)方法:
  1.   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
  2.                 ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  3.         Statement stmt = null;
  4.         try {
  5.           // 获取配置
  6.           Configuration configuration = ms.getConfiguration();
  7.           StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter,
  8.                         rowBounds, resultHandler, boundSql);
  9.           // 得到 PrepareStatement
  10.           stmt = prepareStatement(handler, ms.getStatementLog());
  11.           // 执行查询
  12.           return handler.query(stmt, resultHandler);
  13.         } finally {
  14.           // 关闭 Statement
  15.           closeStatement(stmt);
  16.         }
  17.   }
复制代码
获取Statement的方法为SimpleExecutor#prepareStatement:
  1.   private Statement prepareStatement(StatementHandler handler, Log statementLog)
  2.                 throws SQLException {
  3.         Statement stmt;
  4.         // 获取数据库连接
  5.         Connection connection = getConnection(statementLog);
  6.         // 获取 Statement
  7.         stmt = handler.prepare(connection, transaction.getTimeout());
  8.         // 处理参数设置
  9.         handler.parameterize(stmt);
  10.         return stmt;
  11.   }
复制代码
这个方法先是获取了数据库连接,接着获取Statement,然后处理了参数设置。
关于数据库连接的获取,我们在分析配置文件的剖析时,数据源的配置最终会转化成PooledDataSource或UnpooledDataSource对象,数据库连接就是从数据源来的。
至于Statement的生成,PreparedStatement的实例化操作方法为PreparedStatementHandler#instantiateStatement,这些都是常规的jdbc操作,就不细看了。
处理sql的执行方法为PreparedStatementHandler#query:
  1.   @Override
  2.   public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  3.         PreparedStatement ps = (PreparedStatement) statement;
  4.         // 执行
  5.         ps.execute();
  6.         return resultSetHandler.handleResultSets(ps);
  7.   }
复制代码
SimpleExecutor#doQuery(...)的执行流程如下:
SimpleExecutor的操作就是常规的jdbc操作。
3. ReuseExecutor

ReuseExecutor会缓存PreparedStatement,下一次执行相同的sql可重用。
我们依然分析doQuery(...)方法:
  1.   public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
  2.                 ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  3.         Configuration configuration = ms.getConfiguration();
  4.         StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter,
  5.                         rowBounds, resultHandler, boundSql);
  6.         // 获取 Statement
  7.         Statement stmt = prepareStatement(handler, ms.getStatementLog());
  8.         // 处理查询操作
  9.         return handler.query(stmt, resultHandler);
  10.   }
复制代码
与SimpleExecutor相比,ReuseExecutor的doQuery(...)方法并没关闭Statement.我们来看看Statement的获取操作:
  1. private Statement prepareStatement(StatementHandler handler, Log statementLog)
  2.                 throws SQLException {
  3.         Statement stmt;
  4.         BoundSql boundSql = handler.getBoundSql();
  5.         String sql = boundSql.getSql();
  6.         // 根据sql语句判断是否有Statement缓存
  7.         if (hasStatementFor(sql)) {
  8.           // 有缓存,直接使用
  9.           stmt = getStatement(sql);
  10.           applyTransactionTimeout(stmt);
  11.         } else {
  12.           // 没缓存,获取数据库连接,再获取 Statement
  13.           Connection connection = getConnection(statementLog);
  14.           stmt = handler.prepare(connection, transaction.getTimeout());
  15.           // 缓存 Statement
  16.           putStatement(sql, stmt);
  17.         }
  18.         // 处理参数
  19.         handler.parameterize(stmt);
  20.         return stmt;
  21. }
复制代码
可以看到,ReuseExecutor获取Statement时,会先从缓存里获取,缓存里没有才会新建一个Statement,然后将新建的Statement添加到缓存中。从这里可以看出,ReuseExecutor的Reuse,复用的是Statement。
我们再来看看缓存Statement的结构:
  1. public class ReuseExecutor extends BaseExecutor {
  2.   private final Map<String, Statement> statementMap = new HashMap<>();
  3.   ...
  4.   private Statement getStatement(String s) {
  5.         return statementMap.get(s);
  6.   }
  7.   private void putStatement(String sql, Statement stmt) {
  8.         statementMap.put(sql, stmt);
  9.   }
  10. }
复制代码
由些可见,缓存Statement的是一个Map,key为sql语句,value为Statement.
4. BatchExecutor

BatchExecutor会将修改操作记录在本地,等待程序触发或有下一次查询时才批量执行修改操作,即:
从以上内容来看,这种方式似乎有大坑,列举几点如下:
我们来看下BatchExecutor的更新操作,进入doUpdate(...)方法:
  1.   public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
  2.         final Configuration configuration = ms.getConfiguration();
  3.         final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject,
  4.                         RowBounds.DEFAULT, null, null);
  5.         final BoundSql boundSql = handler.getBoundSql();
  6.         final String sql = boundSql.getSql();
  7.         final Statement stmt;
  8.         // 如果传入的sql是当前保存的 sql,直接使用
  9.         if (sql.equals(currentSql) && ms.equals(currentStatement)) {
  10.           int last = statementList.size() - 1;
  11.           stmt = statementList.get(last);
  12.           applyTransactionTimeout(stmt);
  13.           handler.parameterize(stmt);// fix Issues 322
  14.           BatchResult batchResult = batchResultList.get(last);
  15.           batchResult.addParameterObject(parameterObject);
  16.         } else {
  17.           // 创建连接,获取 Statement
  18.           Connection connection = getConnection(ms.getStatementLog());
  19.           stmt = handler.prepare(connection, transaction.getTimeout());
  20.           handler.parameterize(stmt);    // fix Issues 322
  21.           currentSql = sql;
  22.           currentStatement = ms;
  23.           statementList.add(stmt);
  24.           batchResultList.add(new BatchResult(ms, sql, parameterObject));
  25.         }
  26.         // 保存,等待之后批量执行
  27.         handler.batch(stmt);
  28.         return BATCH_UPDATE_RETURN_VALUE;
  29.   }
复制代码
BatchExecutor有成员变量会记录上一次执行的sql与MappedStatement,假如本次执行的sql与MappedStatement与上一次执行的相同,则直接使用上一次的Statement,否则就新建连接、获取Statement.
得到Statement后,会调用PreparedStatementHandler#batch方法:
  1.   public void batch(Statement statement) throws SQLException {
  2.         PreparedStatement ps = (PreparedStatement) statement;
  3.         ps.addBatch();
  4.   }
复制代码
这个方法并没有执行,只是调用PreparedStatement#addBatch方法,将当前statement保存了起来。
PreparedStatement#addBatch方法如何使用呢?简朴示意下:
  1. // 获取连接
  2. Connection connection = getConnection();
  3. // 预编译sql
  4. String sql = "xxx";
  5. PreparedStatement statement = connection.prepareStatement(sql);   
  6. //记录1
  7. statement.setInt(1, 1);
  8. statement.setString(2, "one");
  9. statement.addBatch();   
  10. //记录2
  11. statement.setInt(1, 2);
  12. statement.setString(2, "two");
  13. statement.addBatch();   
  14. //记录3
  15. statement.setInt(1, 3);
  16. statement.setString(2, "three");
  17. statement.addBatch();   
  18. //批量执行
  19. int[] counts = statement.executeBatch();
  20. // 关闭statment,关闭连接
  21. ...
复制代码
BatchExecutor的doUpdate(...)方法并没有执行sql语句,我们再来看看doQuery(...)方法:
  1.   public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
  2.                 ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  3.         Statement stmt = null;
  4.         try {
  5.           // 处理缓存中的 statements
  6.           flushStatements();
  7.           Configuration configuration = ms.getConfiguration();
  8.           StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject,
  9.                         rowBounds, resultHandler, boundSql);
  10.           // 获取连接,获取Statement,处理参数
  11.           Connection connection = getConnection(ms.getStatementLog());
  12.           stmt = handler.prepare(connection, transaction.getTimeout());
  13.           handler.parameterize(stmt);
  14.           // 执行查询
  15.           return handler.query(stmt, resultHandler);
  16.         } finally {
  17.           // 关闭 Statement
  18.           closeStatement(stmt);
  19.         }
  20.   }
复制代码
doQuery(...)方法会先调用flushStatements()方法,然后再处理查询操作,整个过程基本同SimpleExecutor一致,即”获取数据库连接-获取Statement-处理查询-关闭Statement“等几步。我们重点来看flushStatements()方法的流程.
flushStatements()方法最终调用的是BatchExecutor#doFlushStatements方法,代码如下:
  1.   public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
  2.         try {
  3.           List<BatchResult> results = new ArrayList<>();
  4.           if (isRollback) {
  5.                 return Collections.emptyList();
  6.           }
  7.           // 遍历的statementList,statementList就是缓存statement的结构
  8.           for (int i = 0, n = statementList.size(); i < n; i++) {
  9.                 Statement stmt = statementList.get(i);
  10.                 applyTransactionTimeout(stmt);
  11.                 BatchResult batchResult = batchResultList.get(i);
  12.                 try {
  13.                   // 关键代码:stmt.executeBatch(),批量执行sql
  14.                   batchResult.setUpdateCounts(stmt.executeBatch());
  15.                   ...
  16.                 } catch (BatchUpdateException e) {
  17.                   ...
  18.                 }
  19.                 results.add(batchResult);
  20.           }
  21.           return results;
  22.         } finally {
  23.           ...
  24.         }
  25.   }
复制代码
BatchExecutor#doFlushStatements方法的关键代码就是batchResult.setUpdateCounts(stmt.executeBatch());了 ,其中的stmt.executeBatch()就是批量执行更新操作了。
从以上分析可知,BatchExecutor#doUpdate(...)方法不会执行sql语句,只是把sql语句转换为Statement然后缓存起来,在执行BatchExecutor#doQuery(...)方法时,会先执行缓存起来的Statement,然后再执行查询操作,当然也可以手动调用BatchExecutor#flushStatements方法执行缓存的Statement。
5. CachingExecutor

CachingExecutor差别于以上3种执行器,它是一个装饰类,可以从缓存中获取数据,实际干活的还是以上三种执行器之一:
  1. public class CachingExecutor implements Executor {
  2.   // 具体的执行器
  3.   private final Executor delegate;
  4.   private final TransactionalCacheManager tcm = new TransactionalCacheManager();
  5.   public CachingExecutor(Executor delegate) {
  6.         this.delegate = delegate;
  7.         delegate.setExecutorWrapper(this);
  8.   }
  9.   ...
  10. }
复制代码
从代码来看,它是Executor的子类,其中有一个成员变量delegate,它的类型为Executor,由构造方法传入。也就是说,在创建CachingExecutor时,会传入以上3种执行器之一,CachingExecutor会把它保存到成员变量delegate中。
CachingExecutor的query(...)方法如下:
  1.   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
  2.                 ResultHandler resultHandler) throws SQLException {
  3.         BoundSql boundSql = ms.getBoundSql(parameterObject);
  4.         // 创建缓存key
  5.         CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  6.         return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  7.   }
  8.   @Override
  9.   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
  10.                 ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
  11.           throws SQLException {
  12.         Cache cache = ms.getCache();
  13.         // 操作缓存
  14.         if (cache != null) {
  15.           flushCacheIfRequired(ms);
  16.           if (ms.isUseCache() && resultHandler ** null) {
  17.                 ensureNoOutParams(ms, boundSql);
  18.                 @SuppressWarnings("unchecked")
  19.                 // 从缓存中获取  
  20.                 List<E> list = (List<E>) tcm.getObject(cache, key);
  21.                 if (list ** null) {
  22.                   // 实际处理查询的操作  
  23.                   list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  24.                   // 添加到缓存中
  25.                   tcm.putObject(cache, key, list); // issue #578 and #116
  26.                 }
  27.                 return list;
  28.           }
  29.         }
  30.         return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  31.   }
复制代码
从代码上来看,CachingExecutor在处理查询时,会先从缓存中获取,当缓存中不存在时,就执行具体执行器的query(xxx)方法。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4