Mybatis源码分析(四)Mybatis执行sql的四大组

本文源代码来源于mybatis-spring-boot-starter的2.1.2版本

  SQL语句的执行涉及各个组件,其中比较重要的是ExecutorStatementHandlerParameterHandlerResultSetHandler

  Executor对象在创建Configuration对象的时候创建,并且缓存在Configuration对象里,负责管理一级缓存和二级缓存,并提供是事务管理的相关操作。Executor对象的主要功能是调用StatementHandler访问数据库,并将查询结果存入缓存中(如果配置了缓存的话)。StatementHandler首先通过ParammeterHandler完成SQL的实参绑定,然后通过java.sql.Statement对象执行sql语句并得到结果集ResultSet,最后通过ResultSetHandler完成结果集的映射,得到对象并返回。

Executor

1.1 类图

每一个 SqlSession 都会拥有一个Executor 对象,这个对象负责增删改查的具体操作,我们可以简单的将它理解为 JDBC 中 Statement 的封装版。也可以理解为 SQL 的执行引擎,要干活总得有一个发起人吧,可以把 Executor 理解为发起人的角色。

如上图所示,位于继承体系最顶层的是Executor执行器,它有两个实现类,分别是和BaseExecutorCachingExecutor

  • BaseExecutor

  BaseExecutor是一个抽象类,这种通过抽象的实现接口的方式是的体现,是Executor 的默认实现,实现了大部分 Executor 接口定义的功能,关于查询更新的具体实现由其子类实现。️BaseExecutor 的子类有四个,分别是 SimpleExecutorReuseExecutorCloseExecutorBatchExecutor

  1. SimpleExecutor

  简单执行器,是 MyBatis 中默认使用的执行器,每执行一次 update 或 select,就开启一个Statement 对象,用完就直接关闭 Statement 对象(可以是 Statement 或者是 PreparedStatment 对象)

  1. ReuseExecutor

  可重用执行器,这里的重用指的是重复使用Statement,它会在内部使用一个 Map 把创建的Statement 都缓存起来,每次执行 SQL 命令的时候,都会去判断是否存在基于该 SQL 的 Statement 对象,如果存在Statement 对象并且对应的 connection 还没有关闭的情况下就继续使用之前的 Statement 对象,并将其缓存起来。因为每一个 SqlSession都有一个新的 Executor对象,所以我们缓存在 ReuseExecutor 上的 Statement作用域是同一个 SqlSession

  1. CloseExecutor

  表示一个已关闭的Executor执行期

  1. BatchExecutor

  批处理执行器,用于将多个 SQL 一次性输出到数据库

  • CachingExecutor

  缓存执行器,先从缓存中查询结果,如果存在就返回之前的结果;如果不存在,再委托给Executor delegate 去数据库中取,delegate 可以是上面任何一个执行器。

1.2 Executor的选择和创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
复制代码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);
//创建Executor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

SqlSessionFactory通过Configuration来创建sqlSession,Executor也是在这个时候初始化,我们来看下configuration.newExecutor()这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
复制代码  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
//不指定就用简单执行器
executor = new SimpleExecutor(this, transaction);
}
//如果cacheEnabled为true,则创建CachingExecutor,然后在其内部持有上面创建的Executor
//cacheEnabled默认为true,则默认创建的Executor为CachingExecutor,并且其内部包裹着SimpleExecutor。
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//使用InterceptorChain.pluginAll为executor创建代理对象。Mybatis插件机制。
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

ExecutorType类型可以通过xml标签和JavaApi进行赋值,默认为ExecutorType.SIMPLE

Mybatis插件机制会在其他系列文章里面讲解,这里就不过多介绍了。

1.3 Executor的执行流程

我们从SqlSession的selectList方法入手,其实他们的调用链路都差不多。

1
2
3
4
5
6
7
8
9
10
11
复制代码@Override
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();
}
}

上面说到cacheEnabled默认为true,则默认创建的Executor为CachingExecutor,并且其内部包裹着SimpleExecutor。所以这里executor还是CachingExecutor,我们来看下CachingExecutor的实现

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) {
//执行SimpleExecutor的query方法
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//执行SimpleExecutor的query方法
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

这里的delegate就是SimpleExecutor,也就是说最终还是会执行SimpleExecutor的query实现。到这里,执行器所做的工作就完事了,Executor 会把后续的工作交给继续执行。下面我们来认识一下StatementHandler

StatementHandler

StatementHandler主要负责操作Statement对象与数据库进行交互,在这里我们先回顾一下原生JDBC的相关知识:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码        //1 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2 获得连接
String url = "jdbc:mysql://localhost:3306/test";
Connection conn = DriverManager.getConnection(url,"root", "root");
//3获得语句执行者
Statement st = conn.createStatement();
//4执行SQL语句
ResultSet rs = st.executeQuery("select * from role");
//5处理结果集
while(rs.next()){
// 获得一行数据
Integer id = rs.getInt("id");
String name = rs.getString("name");
System.out.println(id + " , " + name);
}
//6释放资源
rs.close();
st.close();
conn.close();

JDBC通过java.sql.Connection对象创建Statement对象,Statement对象的execute方法就是执行SQL语句的入口。那么对于Mybtais的StatementHandler是用于管理Statement对象的。

2.1 类图

有木有感觉这个继承关系和Executor极为相似,顶层负接口分别有两个实现BaseStatementHandlerRoutingStatementHandler,而BaseStatementHandler分别有三个实现类SimpleStatementHandlerPreparedStatementHandlerCallableStatementHandler
我们不妨先来看下StatementHandler定义的方法:

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
复制代码ublic interface StatementHandler {
//创建Statement对象,即该方法会通过Connection对象创建Statement对象。
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
//对Statement对象参数化,特别是PreapreStatement对象。
void parameterize(Statement statement)
throws SQLException;
//批量执行SQL
void batch(Statement statement)
throws SQLException;
//更新
int update(Statement statement)
throws SQLException;
//查询
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
//根据下标查询
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
// 获取SQl语句
BoundSql getBoundSql();
//获取参数处理器
ParameterHandler getParameterHandler();

}
  • BaseStatementHandler

  它本身是一个抽象类,用于简化StatementHandler 接口实现的难度,属于适配器设计模式体现,它主要有三个实现类:

  1. SimpleStatementHandler

  java.sql.Statement对象创建处理器,管理 Statement 对象并向数据库中推送不需要预编译的SQL语句。

  1. PreparedStatementHandler

  java.sql.PrepareStatement对象的创建处理器,管理Statement对象并向数据中推送需要预编译的SQL语句。

  注意SimpleStatementHandlerPreparedStatementHandler 的区别是 SQL 语句是否包含变量。是否通过外部进行参数传入。SimpleStatementHandler 用于执行没有任何参数传入的 SQL,PreparedStatementHandler 需要对外部传入的变量和参数进行提前参数绑定和赋值。

  1. CallableStatementHandler

  java.sql.CallableStatement对象的创建处理器,管理 Statement 对象并调用数据库中的存储过程。

  • RoutingStatementHandler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
复制代码 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}

}

  与CachExecutor相似,RoutingStatementHandler 并没有对 Statement 对象进行使用,只是根据StatementType 来创建一个代理,代理的就是对应Handler的三种实现类。在MyBatis工作时,使用的StatementHandler 接口对象实际上就是 RoutingStatementHandler对象。

2.2 StatementHandler的选择和创建

以查询为例,前面说到Executor在执行时会先查询缓存在走数据库,我们顺着**queryFromDatabase()方法的doQuery()**方法可以发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
复制代码 @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
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}

2.2.1 configuration.newStatementHandler

1
2
3
4
5
6
7
复制代码 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//插件机制
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}

这里会创建一个RoutingStatementHandler对象,我们刚才说过了,它会根据StatementType来创建对应的Statement对象。StatementType是MappedStatement的一个属性,他在bulid的时候默认为StatementType.PREPARED
PreparedStatementHandler在构建的时候会调用其父类BaseStatementHandlerde的构造函数。ParameterHandlerResultSetHandler也是在这里创建的,我们来看看。

2.2.2 BaseStatementHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
复制代码  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;

this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();

if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}

this.boundSql = boundSql;
//构建parameterHandler
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
//构建resultSetHandler
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}

ParameterHandler

3.1 类图

相比于其他的组件就简单很多了,ParameterHandler 译为参数处理器,负责为 PreparedStatement 的 sql 语句参数动态赋值,这个接口很简单只有两个方法:

  • getParameterObject:用于读取参数
  • setParameters: 用于对 PreparedStatement 的参数赋值

3.2 ParameterHandler的创建

1
2
3
4
5
复制代码public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}

ResultSetHandler

4.1 类图

ResultSetHandler 也很简单,它只有一个实现类DefaultResultSetHandler,主要负责处理两件事

  • 处理 Statement 执行后产生的结果集,生成结果列表。
  • 处理存储过程执行后的输出参数

4.2 ResultSetHandler的创建

1
2
3
4
5
6
复制代码public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}

本文仅对Mybatis执行sql的组件做一个入门级的介绍,后面我们会对执行sql的过程作出详细的讲解。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%