原来Mybatis执行一个sql有这么多类型

Executor 执行器【原来Mybatis执行一个sql有这么多类型】今天分享一下 Executor 。它在框架中是具体sql的执行器,sqlSession(门面模式)封装通用的api,把具体操作委派给 Executor 执行,Executor协同BoundSql,StatementHandler,ParameterHandler 和 ResultSetHandler 完成工作 。
它使用装饰器的方式组织 Executor 对象 。如 CachingExecutor 装饰了SimpleExecutor 提供二级缓存功能 。
可以通过插件机制扩展功能 。mybatisplus 就是通过插件机制扩展的功能 。
下面是更新流程,Executor 处于流程中间蓝色部分,缓存执行器,基础执行器,简单执行器三个 Executor 通过责任链的方式组织起来,各司其职,一同完成执行工作 。,可以感受到它的作用是承上启下 。

原来Mybatis执行一个sql有这么多类型

文章插图
 
执行器介绍
原来Mybatis执行一个sql有这么多类型

文章插图
 
Mybatis 一共提供了四种执行器的实现和一个模板类:
  • 基础执行器 BaseExecutor:实现Executor接口的抽象类,实现了框架逻辑,具体的逻辑委派给子类实现 。一级缓存也是在这里实现的 。
  • 缓存执行器 CachingExecutor:实现了二级缓存,是jvm级别的全局缓存 。
  • 简单执行器 SimpleExecutor:继承自 BaseExecutor,具体执行逻辑的实现 。
  • 重用执行器 ReuseExecutor:相同的 sql 只会预编译一次 。
  • 批处理执行器 BatchExecutor:批处理执行器 使用 JDBC 的batch API 执行 SQL 的批量操作,如insert 或者 update 。select的逻辑和 SimpleExecutor 的实现一样 。
今天介绍 SimpleExecutor,ReuseExecutor 和 BatchExecutor 三个执行器的特定和逻辑, CachingExecutor 的功能是提供二级缓存,暂时不在这里介绍 。
SimpleExecutor简单执行器顾名思义,处理的逻辑比较简单直接,来一个 sql 预编译一个,处理一个 。示例代码如下:
// 创建 SimpleExecutor SimpleExecutor simpleExecutor = new SimpleExecutor(sessionFactory.getConfiguration(),jdbcTransaction);// 获取 MAppedStatement final MappedStatement ms = sessionFactory.getConfiguration().getMappedStatement("example.mapper.UserMapper.getUserByID");final BoundSql boundSql = ms.getBoundSql(1);// 执行 2 次查询simpleExecutor.doQuery(ms, 1, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, boundSql);simpleExecutor.doQuery(ms, 1, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, boundSql);执行结果:
[DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug ==>Preparing: select * from `user` where id = ? [DEBUG][main] m.p.ThresholdInterceptor.intercept ThresholdInterceptor plugin... [DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug ==> Parameters: 1(Integer) [DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug <==Total: 1 [DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug ==>Preparing: select * from `user` where id = ? [DEBUG][main] m.p.ThresholdInterceptor.intercept ThresholdInterceptor plugin... [DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug ==> Parameters: 1(Integer) [DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug <==Total: 1 通过日志看到,虽然执行相同的 sql 但是每次都要执行预编译 。这是一个需要优化的点 。
ReuseExecutorReuseExecutor 对相同 SQL 重复编译做了优化,相同的 sql 的 Statement 只创建一个 。
示例代码上面一样,只是把 SimpleExecutor 换成 ReuseExecutor。从执行我们看到,Preparing 只有一次,执行结果也是正确的:
[DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug ==>Preparing: select * from `user` where id = ? [DEBUG][main] m.p.ThresholdInterceptor.intercept ThresholdInterceptor plugin... [DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug ==> Parameters: 1(Integer) [DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug <==Total: 1 [DEBUG][main] m.p.ThresholdInterceptor.intercept ThresholdInterceptor plugin... [DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug ==> Parameters: 1(Integer) [DEBUG][main] o.a.i.l.j.BaseJdbcLogger.debug <==Total: 1 他是怎么做到的呢?翻开代码看看实现,其实逻辑也很简单,用 SQL 当作 key 保存对应的 Statement 来实现重用 。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;BoundSql boundSql = handler.getBoundSql();String sql = boundSql.getSql();// 关键逻辑,通过 sql 判断是否已经创建了 Statement,如果有则重用 。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;}private final Map<String, Statement> statementMap = new HashMap<>();private boolean hasStatementFor(String sql) {try {Statement statement = statementMap.get(sql);return statement != null && !statement.getConnection().isClosed();} catch (SQLException e) {return false;}}


推荐阅读