前言
前文分析了SqlSession的作用,本文将继续以源码的方式来分析Mybatis中执行器Executor
Executor.java
Mybatis的执行器,处理真正的SQL操作
实际上我们可以这么理解二者的关系,SqlSession
是Mybatis的暴露出来给开发人员的最外层API
,需要提供细致的方法定义
,比如selectOne
, selectList
等,但是实际上真正执行SQL
是需要一个执行器,这个执行器可以不用管你是查单个还是多个,对执行器而言就只是一个query查询
而已,再比如SqlSession需要提供如insert
/update
/delete
等细致的api,而对于执行器来说就是一个update
方法而已,这就是二者的明显的区别
接口定义
public interface Executor {
//
ResultHandler NO_RESULT_HANDLER = null;
// update方法,传入MappedStatement 实例和参数,注意这里是不需要RowBounds参数和ResultHandler的
int update(MappedStatement ms, Object parameter) throws SQLException;
// query方法,传入MappedStatement,参数,以及rowBounds, resultHandler,注意多了一个cacheKey和boundSql参数
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
// 重载的query方法,没有cacheKey和boundSql参数
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
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();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
接口定义分析完了,活
总得有人做的,那就看看Executor
接口具体有哪些实现类
利用IDEA工具我们看到共有以下几种
image.png
- BaseExecutor
- BatchExecutor
- CachingExecutor
- ReuseExecutor
- SimpleExecutor
- ClosedExecutor
其中BaseExecutor是抽象类
,其他实现类(不包括CachingExecutor
)则继承该抽象类,以抽象出共同的执行器功能,ClosedExecutor
是BaseExecutor的静态内部类
,我们不单独分析
这里先提一下:CachingExecutor
是直接实现了Executor接口
的,并没有继承BaseExecutor
,该Executor是用来实现Mybatis的二级缓存
的,后续会以一篇单独的文章来分析Mybatis中的缓存原理
从Mybatis提供的执行器枚举类ExecutorType
来看,主要有三种执行器
BatchExecutor
ReuseExecutor
SimpleExecutor
ExecutorType.emum
public enum ExecutorType {
SIMPLE, REUSE, BATCH
}
我们挑选其中的SimpleExecutor
来分析,分析SimpleExecutor之前我们需要先分析其抽象父类BaseExecutor
BaseExecutor.java
各种执行器实现的抽象父类,抽象了以上三种执行器的共同部分
抽象类声明
public abstract class BaseExecutor implements Executor {...}
共同抽象属性
private static final Log log = LogFactory.getLog(BaseExecutor.class);
// Mybatis事务实例
protected Transaction transaction;
// 装饰器模式,指向包装执行器
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 一级缓存相关,PerpetualCache内部就是持有了一个Map集合作为缓存数据的储存
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
// 持有Mybatis的配置实例
protected Configuration configuration;
protected int queryStack;
// 代表执行器是否关闭
private boolean closed;
抽象的共同属性分析完毕,我们看下其构造方法
构造函数
protected BaseExecutor(Configuration configuration, Transaction transaction) {
// 初始化事务属性
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
// 初始化一个id为LocalCache的缓存对象
this.localCache = new PerpetualCache("LocalCache");
// 初始化一个id为LocalOutputParameterCache的缓存对象
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
// 标识自己是否被关闭
this.closed = false;
// 初始化配置对象
this.configuration = configuration;
// 包装对象先初始化为自己,另外有一个重写的方法setExecutorWrapper可以重置wrapper对象
this.wrapper = this;
}
经过以上,我们知道每一个执行器实例都会持有一个事务Transaction
,一个mybatis配置
属性configuration
及Mybatis查询一级缓存
相关的PerpetualCache属性和一个包装他的包装执行器
实例引用
接下来我们看下BaseExecutor中到底抽象实现了哪些Executor接口中重要的方法,这其中又用到了哪些设计模式
呢?
我们重点看query方法
的实现,其他实现的模式是一样的
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 1. 从MappedStatement对象中根据参数获取BoundSql对象
BoundSql boundSql = ms.getBoundSql(parameter);
// 2. 创建缓存Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 3. 调用重载的query方法
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) {
throw new ExecutorException("Executor was closed.");
}
// 1. 如果配置了flushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存,换句话说就是关闭一级缓存功能
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 1.1. 查询堆栈为0且需要清空缓存,则执行清空缓存
clearLocalCache();
}
List<E> list;
try {
// 2. 查询堆栈 + 1
queryStack++;
// 2.1. 如果此次查询的resultHandler为null(默认为null),则尝试从本地缓存中获取已经缓存的的结果,否则为null
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
// 3. 判断是否又已缓存的结果
if (list != null) {
// 3.1. 已有缓存结果,则处理本地缓存结果输出参数
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 3.2. 没有缓存结果,则从数据库查询结果
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 查询堆栈数 -1
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
@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.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
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);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
// 从数据库查询数据
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方法
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;
}
// 抽象的doQuery方法,真正的查询由子类实现
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
query方法主要有三步:
- 根据此次查询参数(如
{{"phone", "15800000000"}, {"param1", "15800000000"}}
),获取BoundSql
,关于BoundSql可以简单看下[夫礼者
] 的这篇文章https://blog.csdn.net/lqzkcx3/article/details/78370497 -
构建缓存Key
,key规则为:msId + rowBounds/offset + rowBounds/limit + boundSql/sql(原参数占位符已被为?的那种) + 实际的传参value值 + environmentId - 执行查询,这里又会细分为两种情况:
3.1 已存在
一级缓存,则直接返回缓存结果
3.2 不存在缓存,则调用queryFromDatabase
方法,从数据库查询
关于缓存key的生成以及Mybatis一级/二级缓存相关我们会单独以一篇文章来讲解,这里只要知道SimpleExecutor的query方法会先尝试从缓存中获取,如果缓存中没有就从数据库中查询,接下来我们看queryFromDatabase(...)
方法
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 1. 首先向本地缓存中存入一个ExecutionPlaceholder的枚举类占位value
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 2. 执行doQuery方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 3. 执行完成移除这个key?
localCache.removeObject(key);
}
// 4. 将2. 中查询结果存入缓存中
localCache.putObject(key, list);
// 5. 如果MappedStatement的类型为CALLABLE,则向localOutputParameterCache缓存中存入value为parameter的缓存
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
重要的是这里的doQuery方法
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
看到这里是否有些明朗了呢?protected
修饰的抽象doQuery
的方法是交由子类实现的,也就是说真正的查询数据库还是交给了如SimpleExecutor
来做
代码分析到了这里,我们可以看到此处使用了模板方法模式
,利用BaseExecutor抽象出来执行器执行SQL所需
的通用步骤(比如上面的1,2,3步骤等等),真正的查询那一步则抽象出来
,交给子类
自己实现,ok,正式进入SimpleExecutor
SimpleExecutor.java
Mybatis提供的简单执行器,继承自BaseExecutor
public class SimpleExecutor extends BaseExecutor {
// 没有再单独定义任何特有属性,全部继承自BaseExecutor
// 提供一个构造函数,调用父类构造,初始化自父类继承来的属性
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
// 重点doQuery方法实现
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 1. 获取配置实例
Configuration configuration = ms.getConfiguration();
// 2. new一个StatementHandler实例
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 3. 准备处理器,主要包括创建statement以及动态参数的设置
stmt = prepareStatement(handler, ms.getStatementLog());
// 4. 执行真正的数据库操作调用
return handler.<E>query(stmt, resultHandler);
} finally {
// 5. 关闭statement
closeStatement(stmt);
}
}
// 准备Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 1. 获取代理后(增加日志功能)的Connection对象,这里的getConnection方法是BaseExecutor中的,就是从事务实例中获取Connection实例,并返回增加日志功能后的代理
Connection connection = getConnection(statementLog);
// 2. 创建Statement对象(可能是一个SimpleStatement,一个PreparedStatement或CallableStatement)
stmt = handler.prepare(connection, transaction.getTimeout());
// 3. 参数化处理,注意SimpleStatement是静态SQL执行,不需要处理入参的
handler.parameterize(stmt);
// 4. 返回执行前最后准备好的Statement对象
return stmt;
}
}
整个SimpleExecutor的doQuery方法可以简述为以下几步
-
第一步就是通过
MappedStatement
实例获取configuration
实例 -
传入一大堆参数以后,new出来一个
StatementHandler
的实例 -
私有方法
prepareStatement(...)
,就是创建了真正的JDBCStatement
实例(3.1. 调用handler.prepare(...)创建Statement)并对其进行参数化
处理(3.2. 调用handler.parameterize(...)参数化处理Statement,需要注意的是这里的参数化处理实际上是委托给了handler内部持有的ParamterHandler
来做的) -
Statement有了,参数也处理好了(
?号
已经被设置上真实的入参
了),最后就是调用handler.query(...)
方法执行数据库查询操作,再由resultSetHandler
处理查询出的结果后返回
总结
本文只是简单的介绍Mybatis中的Executor接口的执行逻辑,并没有深入分析关于一级/二级缓存方面的源码,关于缓存方面后续会单独一篇文章讲解
简单总结一下本文重点知识:
1:每个SqlSession
内部都持有一个Executor执行器实例
,SqlSession会将数据库执行操作委托
给执行器来做
2:Mybatis将执行器分为三种
:SIMPLE
, REUSE
和BATCH
,并为其抽象出一层BaseExecutor抽象类
,其中定义了三种执行器通用的处理逻辑以及共有属性,而真正三种执行器不同的地方则由自己实现,有点类似模板方法模式的感觉,我们需要记住的是不论哪种执行器
,其最终都是要调用数据库操作的,也就是说其内部均持有一个Transaction
实例,而Transaction
内部则持有真正的JDBC Connection
对象
3:拿SimpleExecutor
来说,其doQuery查询数据库操作来说,需要首先创建一个StatementHandler
,利用这个handler
去执行创建Statement
实例,参数化Statement
,数据库执行操作
以及最终将结果处理
后返回
网友评论