mybatis源码学习之——执行流程(Executor)
声明
本博客只是作为个人学习记录,其中引用到源码阅读网相关图片,若有侵权,告知立删。
本篇博客会随着博主学习进度持续更新修订。
预备知识:
Mybatis执行过程:
Executor执行器涉及的设计方法:模板方法模式、装饰者模式
Executor类图:
Executor 接口源代码:
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
// 执行 insert 、 update 、 delete 三种类型的sql语句
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<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;
// 创建缓存中用的cacheKey
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
// 根据cachekey获取缓存
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);
// 检测Executor对象是否关闭
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
mybatis-config 配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties></properties>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/czcdatabase"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="bookMapper.xml"/>
</mappers>
</configuration>
手动调用执行器实现查询
-
调用SimpleExecutor.doQuery()方法实现查询(跳过一级缓存)
- 读取
mybatis-config.xml
配置文件 - 创建执行器
- 执行sql语句
- 测试源代码
- 读取
private Configuration configuration;
private Connection connection;
private JdbcTransaction transaction;
private MappedStatement ms;
public void creatConnection() throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
configuration = build.getConfiguration();
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/czcdatabase", "root", "root");
//事务对象(mybatis默认的事务处理器)
transaction = new JdbcTransaction(connection);
}
/**
* mybatis simpleExecutor(简单执行器) 测试实验
*/
@Test
public void simpleExecutor() throws Exception {
// 初始化连接(读取配置文件)
creatConnection();
// 创建执行器
SimpleExecutor executor = new SimpleExecutor(configuration, transaction);
//执行Sql
ms = configuration.getMappedStatement("czc.study.mybatis.mapper.BookMapper.getBookByName");
List<Object> objects = executor.doQuery(ms, "西游记", RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER, ms.getBoundSql("西游记"));
System.out.println(objects.get(0));
}
- 输出结果
Book{id=1, bookname='西游记', date=Fri Dec 18 00:00:00 CST 2020
-
调用SimpleExecutor.query()方法实现查询(测试Mybatis一级缓存)
- 一级缓存在BaseExecutor中query方法实现,以query为例分析。
- 测试代码如下
public void baseExecutor() throws Exception {
// 初始化连接(读取配置文件)
creatConnection();
// 创建执行器
SimpleExecutor executor = new SimpleExecutor(configuration, transaction);
//执行Sql
ms = configuration.getMappedStatement("czc.study.mybatis.mapper.BookMapper.getBookByName");
List<Object> objects = executor.query(ms, "西游记", RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER);
// 执行两次sql相同的查询,使其命中缓存(注意一级缓存是线程不安全的)
List<Object> objects2 = executor.query(ms, "西游记", RowBounds.DEFAULT, SimpleExecutor.NO_RESULT_HANDLER);
System.out.println(objects.get(0));
System.out.println(objects2.get(0));
}
- 调试:
- 第一次调用查询命令,缓存里没数据;取值为null,走数据库查询。
- 第二次执行相同sql查询的时候则会命中缓存,并从缓存区取数据
- 一级缓存的实现,其实就是一个Map对象。
- 上述代码 localCache.getObject(key)实现;
// 一级缓存存储容器定义处
public class PerpetualCache implements Cache {
// 省略其余代码...
private final Map<Object, Object> cache = new HashMap<>();
// ...
@Override
public Object getObject(Object key) {
return cache.get(key);
}
}
源码分析
BaseExecutor 是一个实现了 Executor 接口的抽象类,它实现了 Executor 接口的大部分方法,其中就使用了模板方法模式。 BaseExecutor 中主要提供了缓存管理和事务管理的基本功能,继承 BaseExecutor 的子类只要实现四个基本方法来完成数据库的相关操作即可,这四个方法分别是 : doUpdate()方法、 doQuery()方法、 doQueryCursor())方法、 doFlushStatement()方法,其余的功能在 BaseExecutor 中实现。
BaseExecutor 常用子类:ReuseExecutor(可重用执行器)、SimpleExecutor(简单执行器)、BatchExecutor(批处理执行器)
BatchExecutor 在处理批量增删改时,无论连接时是否开启自动提交,都需要手动执行doFlushStatements(),批处理执行器在执行过程中,增删改sql相同时只执行一次预编译。
ReuseExecutor 可重用执行器在遇到相同的sql时,只预编译一次。
CachingExecutor扮演上述三个执行器的装饰者(装饰者设计模式),为执行器提供二级缓存功能支持。
BaseExecutor中各个字段的含义如下:
// Transaction 对象,实现事务的提交、 回滚和关闭操作
protected Transaction transaction;
// 其中封装的 Executor 对象
protected Executor wrapper ;
// 延迟加载队列,后面详述
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 一级缓存,用于缓存该 Executor 对象查询结果集映射得到的结采对象
protected PerpetualCache localCache ;
// 一级缓存,用于缓存输出类型的参数
protected PerpetualCache localOutputPararneterCache;
// 用来记录嵌套查询的层敛,分析 DefaultResultSetHandler 时介绍过嵌套查询,后面还会详细分析该字段
protected int queryStack ;
一级缓存的生命周期与 SqlSession 相同,其实也就与 SqlSession 中封装的 Executor 对象的生命周期相同。当调用 Executor 对象的 close()方法时,该 Executor 对象对应的一级缓存就变得不可用。
BaseExecutor query()方法源代码分析如下:
BaseExecutor. query()方法会首先创建 CacheKey 对象,并根据该 CacheKey 对象查找一级缓存,如果缓存命中则返回缓存中记录的结果对象,如果缓存未命中则查询数据库得到结果集,之后将结果集映射成结果对象并保存到一级缓存中,同时返回结果对象 。
query()方法的具体实现如下:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建 CacheKey 对象,该 CacheKey 对象的组成部分在后面详细介绍
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 调用 query ()的另一个重载,继续后续处理
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
我们来看 CacheKey 对象由哪几部分构成, createCacheKey()方法具体实现如下:
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
// 判断Executor对象是否被关闭;关闭了则抛异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 创建 CacheKey 对象
CacheKey cacheKey = new CacheKey();
// 将 MappedStatement 的 id 添加到 CacheKey 对象中
cacheKey.update(ms.getId());
// 将 offset 添加到 CacheKey 对象中
cacheKey.update(rowBounds.getOffset());
// 将 limit 添加到 CacheKey 对象中
cacheKey.update(rowBounds.getLimit());
// 将 SQL 语句添加到 CacheKey 对象中
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// 获取用户传入的实参,并添加 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;
}
经过createCacheKey()方法便可以对每一次不同的sql请求生成一条唯一的key;由此也可以看出一级缓存命中的一些必要条件;可以清晰地看到,该 CacheKey 对象由 MappedStatement 的 id、对应的 offset 和 limit、 SQL语句(包含“?”占位符)、用户传递的实参以及 Environment 的 id这六部分构成 。
继续来看上述代码中调用的 query()方法的另一重载的具体实现,该重载会根据前面创建的CacheKey 对象查询一级缓存,如果缓存命中则将缓存中记录的结果对象返回,如果缓存未命中,则调用 doQuery()方法完成数据库的查询操作并得到结果对象,之后将结果对象记录到一级缓存中。
具体实现如下:
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());
// 判断Executor对象是否被关闭;关闭了则抛异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 非嵌套查询,并且<select>节点配置的 flushCache 属性为 true 时,才会清空一级缓存
// flushCache 配置项是影响一级缓存中结采对象存活时长的第一个方面
clearLocalCache();
}
List<E> list;
try {
// 用于标记嵌套查询层数
queryStack++;
// 从一级缓存取数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 针对存储过程调用的处理,其功能是在一级缓存命中时,获取缓存中保存的输出类型参数。
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 一级缓存没有数据,则调用数据库
// 其中会调用 doQuery ()方法完成数据库查询,并得到映射后的结采对象, doQuery ()方法是
// 一个抽象方法,也是上述 4 个基本方法之一,由 BaseExecutor 的子类具体实现。
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 当前层查询结束,嵌套层减一
queryStack--;
}
if (queryStack == 0) {
// 存在嵌套子查询时延迟加载处理逻辑(后续博文在分析此处)
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
当缓存没有命中时候,查询数据库,调用 BaseExecutor的queryFromDatabase()方法;基于模板方法模式,具体的查询实现交给了BaseExecutor的实现类去做。此案例中是SimpleExecutor的doQuery()方法实现了查询功能。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 缓存先放一个占位符(防止在查询结果还没出来前;调用缓存查出空数据;在 deferredLoad.load();中实现)
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 调用具体实现BaseExecutor的子类查询方法。
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;
}
SimpleExecutor的doQuery()方法子类
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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}