Executor 是MyBatis的核心接口之一,其中定义了数据库操作的基本方法。在实际应用中经常设计的SqlSession接口的功能,都是基于Executor接口实现的。
SQL 语句的执行涉及多个组件,包括 MyBatis 的四大核心,它们是: Executor
、StatementHandler
、ParameterHandler
、ResultSetHandler
。SQL 的执行过程可以用下面这幅图来表示。
MyBatis 层级结构各个组件的介绍:
- SqlSession: ,它是 MyBatis 核心 API,主要用来执行命令,获取映射,管理事务。接收开发人员提供 Statement Id 和参数。并返回操作结果。
- Executor :执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成以及查询缓存的维护。
- StatementHandler : 封装了JDBC Statement 操作,负责对 JDBC Statement 的操作,如设置参数、将Statement 结果集转换成 List 集合。
- ParameterHandler : 负责对用户传递的参数转换成 JDBC Statement 所需要的参数。
- ResultSetHandler : 负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合。
- TypeHandler : 用于 Java 类型和 JDBC 类型之间的转换。
- MappedStatement : 动态 SQL 的封装
- SqlSource : 表示从 XML 文件或注释读取的映射语句的内容,它创建将从用户接收的输入参数传递给数据库的 SQL。
- Configuration: MyBatis 所有的配置信息都维持在 Configuration 对象之中。
一、Executor 说明
MyBatis 提供 Executor 接口实现如下图。
在这些 Executor接口实现中涉及两种设计模式:模板模式和装饰器模式。
装饰器模式:二级缓存 CachingExecutor扮演了装饰器的角色,为Executor添加二级缓存功能。
模板模式:BaseExecutor是实现Executor接口的抽象类,其中主要提供了缓存管理和事务管理的基本功能。而其继承其子类只有实现四个方法分别 是: doUpdate()方法、 doQue可()方法、 doQueryCursor()方法、 doFlushStatement()方法,其余的功 能在 BaseExecutor 中实现。
1.1 Executor 接口中定义的方法如下:
/**
* @author Clinton Begin
* Mybatis核心接口之一,定义了数据库操作最基本的方法。
* Session的功能都是基于他来实现。
*/
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
//执行增删改的三种类型SQL语句
int update(MappedStatement ms, Object parameter) throws SQLException;
//执行select类型的sql语句,返回结果为集合对象
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
//执行select类型的sql语句,返回结果为集合对象 这个方法在实现类中还是调用上面的方法执行
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
//执行select类型的sql语句,返回结果为游标
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
//批量执行SQL语句
List<BatchResult> flushStatements() throws SQLException;
//提交事务
void commit(boolean required) throws SQLException;
//回滚事务
void rollback(boolean required) throws SQLException;
//创建缓存key值
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
//是否缓存
boolean isCached(MappedStatement ms, CacheKey key);
//清空一级缓存
void clearLocalCache();
//延迟加载一级缓存数据
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
//获取事务对象
Transaction getTransaction();
//关闭Executor对象
void close(boolean forceRollback);
//对象是否关闭
boolean isClosed();
//
void setExecutorWrapper(Executor executor);
}
1.2 BaseExecutor 抽象类
抽象类,实现了executor接口的大部分方法,主要提供了缓存管理和事务管理的能力,其他子类需要实现的抽象方法doUpdate,doQuery等方法;
下图是BaseExecutor的query()方法执行过程:
BaseExecutor 中各个字段的含义如下:
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;//事务对象,实现事务的提交、回滚和关闭操作
protected Executor wrapper;//装饰器模式 封装Executor对象
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;//延迟加载队列
protected PerpetualCache localCache;//一级缓存的实现
protected PerpetualCache localOutputParameterCache;//一级缓存用于缓存输出的结果
protected Configuration configuration;//全局唯一configuration对象的引用
protected int queryStack;//用于嵌套查询的的层数
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
下面是query实现方法:代码中调用的 query()方法的另一重载的具体实现,该重载会根据前面创建的 CacheKey 对象查询一级缓存,如果缓存命中则将缓存中记录的结果对象返回,如果缓存未命中, 则调用 doQuery()方法完成数据库的查询操作并得到结果对象,之后将结果对象记录到一级缓存 中。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取sql语句信息,包括占位符,参数等信息
BoundSql boundSql = ms.getBoundSql(parameter);
//创建CacheKey 对象,该值作为一级缓存的KEY
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
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) {//检查当前executor对象是否关闭
throw new ExecutorException("Executor was closed.");
}
//非嵌套查询,并且<select>节点配置的 flushCache 属性为 true 时,才会清空一级缓存 I
// flushCache 配置项是影响一级缓存中结采对象存活时长的第一个方面
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();
}
// issue #601
deferredLoads.clear();
//如果当前sql的一级缓存配置为STATEMENT,查询完既清空一集缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
1.2.1 一级缓存
MyBatis 作为一个功能强大的 ORM 框架,也提供了缓存的功能, 其缓存设计为两层结构, 分别为一级缓存和二级缓存。
一级缓存是会话级别的缓存,在 MyBatis 中每创建一个 Sq!Session 对象,就表示开启一次数据库会话。在一次会话中,应用程序可能会在短时间内,例如一个事务内,反复执行完全相 同的查询语句,如果不对数据进行缓存,那么每一次查询都会执行一次数据库查询操作,而多 次完全相同的、时间间隔较短的查询语句得到的结果集极有可能完全相同,这也就造成了数据 库资源的浪费。
一级缓存的生命周期与 SqlSession 相同,其实也就与 SqI Session 中封装的 Executor 对象的 生命周期相同。当调用 Executor对象的 close()方法时,该 Executor 对象对应的一级缓存就变得 不可用。一级缓存中对象的存活时间受很多方面的影响,例如,在调用 Executor.update()方法时, 也会先请空一级缓存。其他影响一级缓存中数据的行为,我们在分析 BaseExecutor 的具体实现 时会详细介绍。一级缓存默认是开启 的, 一般情况下,不需要用户进行特殊配置。
//创建CacheKey 对象,该值作为一级缓存的KEY
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
该 CacheKey 对象由 MappedStatement 的 id、对应的 offset 和 limit、 SQL 语句(包含“?”占位符)、用户传递的实参以及 Environment 的 id 这五部分构成。
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey(); //创建 CacheKey 对象
cacheKey.update(ms.getId());//将 MappedStatement 的 id 添加到 CacheKey 对象中
cacheKey.update(rowBounds.getOffset());//将 offset 添加到 CacheKey 对象中
cacheKey.update(rowBounds.getLimit());//将 limit 添加到 CacheKey 对象中
cacheKey.update(boundSql.getSql());//将 SQL 语句添加到 CacheKey 对象中
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
//获取用户传入的实参,并添加CacheKey 对象中
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);//将实参添加到 CacheKey 对象中
}
}
//如果 Environment 的 id 不为空,则将其添加到 CacheKey 中
//区分不同的数据源
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
1.2.2 事务相关处理
BaseExecutor.commit()方法首先会清空一级缓存、调用 flushStatements()方法,最后才根据 参数决定是否真正提交事务。 commit()方法的实现如下:
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
//情况一级缓存
clearLocalCache();
//执行缓存的 SQL 语句,其中调用了 flushStatements(false )方法
flushStatements();
if (required) {//判断是否需要提交事务
transaction.commit();
}
}
BaseExecutor.rollback()方法的实现与 commit()实现类似, 同样会根据参数决定是否真正回 滚事务,区别是其中调用的是 flushStatements()方法的 isRollBack 参数为 true, 这就会导致 Executor 中缓存的 SQL 语句全部被忽略(不会被发送到数据库执行)。
BaseExecutor.close()方法首先会调用 rollback()方法忽略缓存的 SQL 语句,之后根据参数决 定是否关闭底层的数据库连接
1.2.3 真正查询数据库查询数据:
真正查收数据库调用抽象方法doQuery,方法查询数据库并返回结果,使用了模板模式,其可选的实现包括:SimpleExecutor、ReuseExecutor、BatchExecutor。
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 {
//调用抽象方法doQuery,方法查询数据库并返回结果,
// 可选的实现包括:SimpleExecutor、ReuseExecutor、BatchExecutor
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;
}
BaseExecutor. update()方法负责执行 insert、update、delete 三类 SQL语句,它是调用 doUpdate() 模板方法实现的。在调用 doUpdate()方法之前会清空缓存,因为执行 SQL 语句之后,数据库中 的数据已经更新, 一级缓存的内容与数据库中的数据可能己经不一致了,所以需要调用 clearLocalCache()方法清空一级缓存中的“脏数据”。
二 、Executor的实现类解读
- SimpleExecutor :默认配置,使用PrepareStatement对象访问数据库,每次访问都要创建新的PrepareStatement对象;
- ReuseExecutor:使用预编译PrepareStatement对象访问数据库,访问时,会重用缓存中的statement对象;
- BatchExecutor:实现批量执行多条SQL语句的能力;
- CachingExecutor :是一个 Executor 接口的装饰器,它为 Executor 对象增加了二级缓存的相关 功能.
2.1 SimpleExecutor
SimpleExecutor 继承了 BaseExecutor 抽象类, 它是最简单的 Executor 接口实现。正如前面 所说, Executor 使用了模板方法模式, 一级缓存等固定不变的操作都封装到了 BaseExecutor 中, 在 SimpleExecutor 中就不必再关心一级缓存等操作,只需要专注实现4个基本方法的实现即可。
首先来看 SimpleExecutor.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();//获取获取configuration对象
//创建 StatementHandler 对象,实际返回的是 RoutingStatementHandler 对象,
//其中根据 MappedStatement.statementType 选择具体的 StatementHandler 实现
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
stmt = prepareStatement(handler, ms.getStatementLog());
//通StatementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
1、创建 StatementHandler 对象(这个后继会有介绍)。封装了JDBC Statement 操作,负责对 JDBC Statement 的操作,如设置参数、将Statement 结果集转换成 List 集合。
实际返回的是 RoutingStatementHandler 对象,其中根据 MappedStatement.statementType 选择具体的 StatementHandler 实现。
//创建 StatementHandler 对象,实际返回的是 RoutingStatementHandler 对象,
//其中根据 MappedStatement.statementType 选择具体的 StatementHandler 实现
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//生产拦截器对象 方便扩展
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
/**
* Excutor组件真正实例化的子类,使用静态代理模式,根据上下文决定创建哪个具体实体类;
* @author Clinton Begin
*/
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;//底层封装的真正的StatementHandler对象
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//RoutingStatementHandler最主要的功能就是根据mappedStatment的配置,生成一个对应的StatementHandler对象并赋值给delegate
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());
}
}
......
}
2、StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
//StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
stmt = prepareStatement(handler, ms.getStatementLog());
//创建Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取connection对象的动态代理,添加日志能力;
Connection connection = getConnection(statementLog);
//通过不同的StatementHandler,利用connection创建(prepare)Statement
stmt = handler.prepare(connection, transaction.getTimeout());
//使用parameterHandler处理占位符
handler.parameterize(stmt);
return stmt;
}
protected Connection getConnection(Log statementLog) throws SQLException {
//获取数据库连接
Connection connection = transaction.getConnection();
//如果是Debug 则获取其动态代理对象添加打印日志的功能
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
2.2 ReuseExecutor
使用预编译PrepareStatement对象访问数据库,访问时,会重用缓存中的statement对象;
ReuseExecutor 提供了 Statement 重用的功能, ReuseExecutor 中通过 statementMap 字段 (HashMap<String, Statement>类型) 缓存使用过的 Statement 对象, key 是 SQL 语句, value 是 SQL 对应的 Statement 对象。 ReuseExecutor.doQuery() 、doUpdate()等方法的实现与 SimpleExecutor 中对 应方法的实现一样,区别在于其中调用的 preparestatement()方法, SimpleExecutor 每次都会通过 JDBC Connection 创建新的 Statement 对象,而 ReuseExecutor 则会先尝试重用 StaternentMap 中 缓存的 Statement 对象。
ReuseExecutor. prepareStatement()方法的具体实现如下:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();//获取sql语句
if (hasStatementFor(sql)) {//根据sql语句检查是否缓存了对应的Statement
stmt = getStatement(sql);//获取缓存的Statement
applyTransactionTimeout(stmt);//设置新的超时时间
} else {//缓存中没有statment,创建statment过程和SimpleExecutor类似
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);//放入缓存中
}
//使用parameterHandler处理占位符
handler.parameterize(stmt);
return stmt;
}
2.3 BatchExecutor(这里就先不做具体分析)
应用系统在执行一条 SQL 语句时,会将 SQL 语句以及相关参数通过网络发送到数据库系 统。对于频繁操作数据库的应用系统来说,如果执行一条 SQL 语句就向数据库发送一次请求, 很多时间会浪费在网络通信上。使用批量处理的优化方式可以在客户端缓存多条 SQL 语句,并 在合适的时机将多条 SQL 语句打包发送给数据库执行,从而减少网络方面的开销,提升系统的 性能。
2.4 CachingExecutor(二级缓存装饰器)
CachingExecutor 是一个 Executor 接口的装饰器,它为 Executor 对象增加了二级缓存的相关 功能。 在开始介绍 CachingExecutor 的具体实现之前,先来简单介绍一下 MyBatis 中的二级缓存 及其依赖的相关组件。
1) 首先是 mybatis-config.xml 配置文件中的 cacheEnabled 配置,它是二级缓存的总开关。 只有当该配置设置为 true 时,后面两项的配置才会有效果, cacheEnabled 的默认值为 true。具 体配置如下:
<settings>
<!-- 这个配置使全局的映射器启用或禁用缓存 -->
<setting name="cacheEnabled" value="true" />
</settings>
2) 映射配置文件的解析流程时提到,映射配置文件中可以配置<cache>节点 或<cached-ref>节点。
3 )最后一个配置项是<select>节点中的 useCache 属性,该属性表示查询操作产生的结果 对象是否要保存到二级缓存中。 useCache 属性的默认值是 true。
CachingExecutor.query()方法执行查询操作的步骤如下:
- 获取 BoundSql 对象,创建查询语句对应的 CacheKey 对象。
- 检测是否开启了二级缓存,如果没有开启二级缓存,则直接调用底层 Executor 对象的 query()方法查询数据库。如果开启了二级缓存,则继续后面的步骤。
- 检测查询操作是否包含输出类型的参数,如果是这种情况,则报错。
- 调用 TransactionalCacheManager.getObject()方法查询二级缓存,如果二级缓存中查找 到相应的结果对象,则直接将该结果对象返回。
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取sql语句信息,包括占位符,参数等信息
BoundSql boundSql = ms.getBoundSql(parameterObject);
//拼装缓存的key值
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//从MappedStatement中获取二级缓存
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) {
//二级缓存为空,才会调用BaseExecutor.query
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
通过对SimpleExecutor.doQuery()方法的解读发现,Executor是个指挥官,它在调度三个小弟工作:
- StatementHandler:它的作用是使用数据库的Statement或PrepareStatement执行操作,启承上启下作用;
- ParameterHandler:对预编译的SQL语句进行参数设置,SQL语句中的的占位符“?”都对应 BoundSql.parameterMappings集合中的一个元素,在该对象中记录了对应的参数名称以及该参数的相关属性
- ResultSetHandler:对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型;
对于这个三个执行器在下篇文章会有介绍。