本文源代码来源于mybatis-spring-boot-starter的2.1.2版本
SQL语句的执行涉及各个组件,其中比较重要的是Executor
,StatementHandler
,ParameterHandler
和ResultSetHandler
。
Executor
对象在创建Configuration
对象的时候创建,并且缓存在Configuration
对象里,负责管理一级缓存和二级缓存,并提供是事务管理的相关操作。Executor
对象的主要功能是调用StatementHandler
访问数据库,并将查询结果存入缓存中(如果配置了缓存的话)。StatementHandler
首先通过ParammeterHandler
完成SQL的实参绑定,然后通过java.sql.Statement
对象执行sql语句并得到结果集ResultSet
,最后通过ResultSetHandler
完成结果集的映射,得到对象并返回。
Executor
1.1 类图
每一个 SqlSession
都会拥有一个Executor
对象,这个对象负责增删改查的具体操作,我们可以简单的将它理解为 JDBC 中 Statement 的封装版。也可以理解为 SQL 的执行引擎,要干活总得有一个发起人吧,可以把 Executor 理解为发起人的角色。
如上图所示,位于继承体系最顶层的是Executor
执行器,它有两个实现类,分别是和BaseExecutor
和CachingExecutor
。
BaseExecutor
BaseExecutor
是一个抽象类,这种通过抽象的实现接口的方式是的体现,是Executor 的默认实现,实现了大部分 Executor 接口定义的功能,关于查询更新的具体实现由其子类实现。️BaseExecutor 的子类有四个,分别是 SimpleExecutor
、ReuseExecutor
、CloseExecutor
和 BatchExecutor
。
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的选择和创建
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()
这个方法:
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方法入手,其实他们的调用链路都差不多。
@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
的实现
@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 注册驱动
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极为相似,顶层负接口分别有两个实现
BaseStatementHandler
和 RoutingStatementHandler
,而BaseStatementHandler分别有三个实现类 SimpleStatementHandler
、 PreparedStatementHandler
、 CallableStatementHandler
。
我们不妨先来看下StatementHandler
定义的方法:
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语句。
注意:SimpleStatementHandler
和 PreparedStatementHandler
的区别是 SQL 语句是否包含变量。是否通过外部进行参数传入。SimpleStatementHandler
用于执行没有任何参数传入的 SQL,PreparedStatementHandler
需要对外部传入的变量和参数进行提前参数绑定和赋值。
1、 CallableStatementHandler
java.sql.CallableStatement对象的创建处理器,管理 Statement 对象并调用数据库中的存储过程。
RoutingStatementHandler
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()**方法可以发现:
@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
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
的构造函数。ParameterHandler
和ResultSetHandler
也是在这里创建的,我们来看看。
2.2.2 BaseStatementHandler
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的创建
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的创建
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的过程作出详细的讲解。