1 JDBC回顾
- 回顾JDBC执行过程
添加jar包 -> 获得连接 -> 预编译SQL -> 执行SQL,读取结果 -> 关闭事务
public static void main(String[] args) throws Exception {
// 1、注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
// 2、建立连接
Connection con =
DriverManager.getConnection("jdbc:mysql://localhost:3306/test?
useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT", "root", "root");
// 3、编写sql,进行预编译
String sql = " select * from tb_brand;";
PreparedStatement ps = con.prepareStatement(sql);
// 4、执行查询,得到结果集
ResultSet rs = ps.executeQuery();
while (rs.next()) {
int bid = rs.getInt("bid");
String bname = rs.getString("bname");
System.out.println("====> bid=" + bid + "\tbname=" + bname);
}
//5、关闭事务
rs.close();
ps.close();
con.close();
}
- MyBatis对JDBC封装的执行过程
2 MyBatis的核心执行组件介绍
在Mybatis中,SqlSession对数据库的操作,将委托给执行器Executor来完成都将委托给执行器
Executor来完成;
Mybatis执行过程,主要的执行模块是: SqlSession->Executor->StatementHandler->数据库
四个核心组件:
① 动态代理 MapperProxy
② SQL会话 SqlSession
③ 执行器 Executor
④ JDBC处理器 StatementHandler
2.1 SqlSession
SqlSession采用了门面模式方便来让我们使用。他不能跨线程使用,一级缓存生命周期和它一致;
基本功能:增删改查基本的操作功能;
辅助功能:提交、关闭会话;
门面模式:提供一个统一的门面接口API,使得系统更加容易使用。
2.2 Executor
基本功能:改、查、维护缓存;
辅助功能:提交、关闭执行器、批处理刷新;
Executor 主要负责维护一级缓存和二级缓存,并提供事务管理的相关操作,它会将数据库相关操作
委托给 StatementHandler完成。
2.3 StatementHandler
经过执行器处理后交给了StatementHandler(声明处理器);
主要作用就是:参数处理、结果处理;
StatementHandler 首先通过 ParameterHandler 完成 SQL 语句的实参绑定,然后通过
java.sql.Statement 对象执行 SQL 语句并得到结果集,最后通过 ResultSetHandler 完成结果
集的映射,得到结果对象并返回。
3 Executor执行器分析
3.1 JDBC中的执行器
JDBC有三种执行器分别是Statement(简单执行器)、PreparedStatement(预处理执行器)、
CallableStatement(存储过程执行器)
Statement:基本功能:执行静态SQL
PreparedStatement:设置预编译,防止SQL注入
CallableStatement:设置出参、读取参数(用于执行存储过程)
3.2 Mybatis执行器
Executor继承结构分析
Mybatis给我们提供了三种执行器,分别是 :
SimpleExecutor(简单执行器)、
ResuseExecutor(可重用执行器)、
BathExecutor(批处理执行器)
这三个执行器继承了一个BaseExecutor(基础执行器),而这个基础执行器实现了Executor接口,其中
简单执行器是默认的执行器。
其实还有一种执行器CachingExecutor(二级缓存执行器)你开启二级缓存则会实例化它,在 BaseExecutor
的基础上,实现二级缓存功能。 (注意: BaseExecutor 的本地缓存,就是一级缓存。)
3.2.1 Executor接口
org.apache.ibatis.executor.Executor ,执行器接口。
主要定义了以下内容:
读和写操作相关的方法
事务相关的方法
缓存相关的方法
设置延迟加载的方法
设置包装的 Executor 对象的方法
public interface Executor {
// 空 ResultHandler 对象的枚举
ResultHandler NO_RESULT_HANDLER = null;
// 更新 or 插入 or 删除,由传入的 MappedStatement 的 SQL 所决定
int update(MappedStatement ms, Object parameter) throws SQLException;
// 查询,带 ResultHandler + CacheKey + BoundSql
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws
SQLException;
// 查询,带 ResultHandler
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler) throws SQLException;
// 查询,返回值为 Cursor
<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);
// 判断是否缓存
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();
// 设置包装的 Executor 对象
void setExecutorWrapper(Executor executor);
}
3.2.2 BaseExecutor(基础执行器)
基础执行器:维护一级缓存,是simple、reuse、batch这三个执行器的父类。主要逻辑是维护缓 存,其他实现交给子类。 org.apache.ibatis.executor.BaseExecutor 实现 Executor 接口,提 供骨架方法,从而使子类只要实现指定的几个抽象方法即可。
/**
* 事务对象
*/
protected Transaction transaction;
/**
* 包装的 Executor 对象
*/
protected Executor wrapper;
/**
* DeferredLoad( 延迟加载 ) 队列
*/
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
/**
* 本地缓存,即一级缓存
*/
protected PerpetualCache localCache;
/**
* 本地输出类型的参数的缓存
*/
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
/**
* 记录嵌套查询的层级
*/
protected int queryStack;
/**
* 是否关闭
*/
private boolean closed;
// ************************
/**
clearLocalCache() 方法,清理一级(本地)缓存
*/
@Override
public void clearLocalCache() {
if (!closed) {
// 清理 localCache
localCache.clear();
// 清理 localOutputParameterCache
localOutputParameterCache.clear();
}
}
// createCacheKey(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, BoundSql boundSql) 方法,创建 CacheKey 对象
// isCached(MappedStatement ms, CacheKey key) 方法,判断一级缓存是否存在
// query方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException {
// <1> 获得 BoundSql 对象
BoundSql boundSql = ms.getBoundSql(parameter);
// <2> 创建 CacheKey 对象
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// <3> 查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// update方法
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an
update").object(ms.getId());
// <1> 已经关闭,则抛出 ExecutorException 异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// <2> 清空本地缓存
clearLocalCache();
// <3> 执行写操作
return doUpdate(ms, parameter);
}
3.2.3 SimpleExecutor(简单执行器)
每次读或写操作都会创建一个新的预处理器(PrepareStatement);
每次执行的的SQL都会进行一次预编译;
执行完成后,关闭该 Statement 对象。
org.apache.ibatis.executor.SimpleExecutor ,继承 BaseExecutor 抽象类,简单的 Executor 实现
类。
// 查询
@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();
// <1> 创建 StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(wrapper,
ms, parameter, rowBounds, resultHandler, boundSql);
// <2> 初始化 StatementHandler 对象
stmt = prepareStatement(handler, ms.getStatementLog());
// <3> 执行 StatementHandler ,进行读操作
return handler.query(stmt, resultHandler);
} finally {
// <4> 关闭 StatementHandler 对象
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog)
throws SQLException {
Statement stmt;
// <2.1> 获得 Connection 对象
Connection connection = getConnection(statementLog);
// <2.2> 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// <2.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);
return stmt;
}
// 更新
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(this, ms,
parameter, RowBounds.DEFAULT, null, null);
// 初始化 StatementHandler 对象
stmt = prepareStatement(handler, ms.getStatementLog());
// <3> 执行 StatementHandler ,进行写操作
return handler.update(stmt);
} finally {
// 关闭 StatementHandler 对象
closeStatement(stmt);
}
}
3.2.4 ReuseExecutor(可重用执行器)
每次开始读或写操作,以sql作为key,查找Statement对象优先从缓存中获取对应的 Statement
对象。如果不存在,才进行创建。( 只要是相同的SQL只会进行一次预处理)
执行完成后,不关闭该 Statement 对象,而是放置于Map<String, Statement>内,供下一次使
用。
其它的,和 SimpleExecutor 是一致的。
原理:
//可重用的执行器内部用了一个map,用来缓存SQL语句对应的Statement对象
//Key是我们的SQL语句,value是我们的Statement对象
private final Map<String, Statement> statementMap = new HashMap<String,
Statement>();
org.apache.ibatis.executor.ReuseExecutor ,继承 BaseExecutor 抽象类,可重用的 Executor 实
现类。
// doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms,
parameter, rowBounds, resultHandler, boundSql);
// 初始化 StatementHandler 对象
Statement stmt = prepareStatement(handler, ms.getStatementLog());
// 执行 StatementHandler ,进行读操作
return handler.query(stmt, resultHandler);
}
// doUpdate
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler 对象
StatementHandler handler = configuration.newStatementHandler(this, ms,
parameter, RowBounds.DEFAULT, null, null);
// 初始化 StatementHandler 对象
Statement stmt = prepareStatement(handler, ms.getStatementLog());
// 执行 StatementHandler ,进行写操作
return handler.update(stmt);
}
private Statement prepareStatement(StatementHandler handler, Log statementLog)
throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// 存在
if (hasStatementFor(sql)) {
// <1.1> 从缓存中获得 Statement 或 PrepareStatement 对象
stmt = getStatement(sql);
// <1.2> 设置事务超时时间
applyTransactionTimeout(stmt);
// 不存在
} else {
// <2.1> 获得 Connection 对象
Connection connection = getConnection(statementLog);
// <2.2> 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// <2.3> 添加到缓存中
putStatement(sql, stmt);
}
// <2> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);
return stmt;
}
// 判断是否存在对应的 Statement 对象
private boolean hasStatementFor(String sql) {
try {
return statementMap.keySet().contains(sql) &&
!statementMap.get(sql).getConnection().isClosed();
} catch (SQLException e) {
return false;
}
}
注意区别
注意:
ReuseExecutor 考虑到重用性,但是 Statement 最终还是需要有地方关闭。答案就在
#doFlushStatements(boolean isRollback) 方法中。而 BaseExecutor 在关闭 #close() 方法
中,最终也会调用该方法,从而完成关闭缓存的 Statement 对象们
BaseExecutor 在提交或者回滚事务方法中,最终也会调用该方法,也能完成关闭缓存的 Statement
对象们。
3.2.5 BatchExecutor(批处理执行器)
批处理只对增删改SQL有效(没有select,JDBC批处理不支持select);
将所有增删改sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓
存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行
executeBatch()批处理的;
执行sql需要满足三个条件才能使用同一个Statement(使用同一个Statement是为了压缩体积、
减少SQL预处理)
1.sql相同
2.同一个MappedStatement(sql标签的配置都在这里面)
3.执行的顺序必须是连续的
org.apache.ibatis.executor.BatchExecutor ,继承 BaseExecutor 抽象类,批量执行的 Executor
实现类。
public class BatchExecutor extends BaseExecutor {
/**
* Statement 数组
*/
private final List<Statement> statementList = new ArrayList<>();
/**
* BatchResult 数组
*
* 每一个 BatchResult 元素,对应一个 {@link #statementList} 的 Statement 元素
*/
private final List<BatchResult> batchResultList = new ArrayList<>();
/**
* 当前 SQL
*/
private String currentSql;
/**
* 当前 MappedStatement 对象
*/
private MappedStatement currentStatement;
//doUpdate
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws
SQLException {
final Configuration configuration = ms.getConfiguration();
// <1> 创建 StatementHandler 对象
final StatementHandler handler = configuration.newStatementHandler(this,
ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
// <2> 如果匹配最后一次 currentSql 和 currentStatement ,则聚合到 BatchResult
中
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
// <2.1> 获得最后一次的 Statement 对象
int last = statementList.size() - 1;
stmt = statementList.get(last);
// <2.2> 设置事务超时时间
applyTransactionTimeout(stmt);
// <2.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);
// <2.4> 获得最后一次的 BatchResult 对象,并添加参数到其中
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
// <3> 如果不匹配最后一次 currentSql 和 currentStatement ,则新建 BatchResult
对象
} else {
// <3.1> 获得 Connection
Connection connection = getConnection(ms.getStatementLog());
// <3.2> 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// <3.3> 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt); //fix Issues 322
// <3.4> 重新设置 currentSql 和 currentStatement
currentSql = sql;
currentStatement = ms;
// <3.5> 添加 Statement 到 statementList 中
statementList.add(stmt);
// <3.6> 创建 BatchResult 对象,并添加到 batchResultList 中
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// <4> 批处理
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
//doQuery
//和 SimpleExecutor 的该方法,逻辑差不多。区别在发生查询之前,先调用
#flushStatements() 方法,刷入批处理语句。
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException {
Statement stmt = null;
try {
// *******************刷入批处理语句
flushStatements();
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler 对象
StatementHandler handler =
configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds,
resultHandler, boundSql);
// 获得 Connection 对象
Connection connection = getConnection(ms.getStatementLog());
// 创建 Statement 或 PrepareStatement 对象
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置 SQL 上的参数,例如 PrepareStatement 对象上的占位符
handler.parameterize(stmt);
// 执行 StatementHandler ,进行读操作
return handler.query(stmt, resultHandler);
} finally {
// 关闭 StatementHandler 对象
closeStatement(stmt);
}
}
}
doUpdate代码上 if (sql.equals(currentSql) && ms.equals(currentStatement)) 可以
看出上一个添加的是否是这个sql(currentSql)并且是同一个MappedStatement
currentStatement(映射语句);
满足条件就将参数放到当前这个BatchResult对象中的参数。属性是(parameterObject)
【batchResult.addParameterObject(parameterObject);】
不满足条件则获取一个Statement实例 再实例化BatchResult对象。最后放到list中去,再给
current和MappedStatement currentStatement赋值
批处理提交必须执行flushStatements才会生效(会将一个个的Statement提交)可以减少与数据库
交互次数
3.2.6 CachingExecutor(二级缓存执行器)
CachingExecutor二级缓存执行器,属于缓存章节内容,在后面缓存章节详细讲解;
3.3 Executor的创建:
在上面的学习中,我们已经理解了各种 Executor 的实现代码。
那么,Executor 对象究竟在 MyBatis 中,是如何被创建的呢?
其实Configuration 类中,提供 newExecutor 方法,代码如下:
/**
* 创建 Executor 对象 -----------------在创建SqlSession执行
*
* @param transaction 事务对象
* @param executorType 执行器类型
* @return Executor 对象
*/
public Executor newExecutor(Transaction transaction, ExecutorType executorType)
{
// <1> 获得执行器类型
//可以通过在 mybatis-config.xml 配置 <setting name="defaultExecutorType"
value="" />
executorType = executorType == null ? defaultExecutorType : executorType; //
使用默认
executorType = executorType == null ? ExecutorType.SIMPLE : executorType; //
判断默认
// <2> 3个分支3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor: 默认为
SimpleExecutor 对象
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);
}
// <3> 如果开启缓存,创建 CachingExecutor 对象,装饰者模式进行包装
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// <4> 应用插件
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
4 一级缓存
- 概述:
当我们使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据
库会话,建立一个简单的缓存,将每次查询到的结果缓存起来,当下次查询的时候,如果判断先前有
个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询
了。
说明:对于会话(Session)级别的数据缓存,我们称之为一级数据缓存,简称一级缓存。
特点:
一级缓存是默认开启的
一级缓存是基于SqlSession生命周期的
2) 一级缓存示意图:
SqlSession – DefaultSqlSession
Executor – BaseExecutor – “PerpetualCache localCache”
- 测试一级缓存:
测试1: 测试多次查询同一条数据
@Test
public void testLocalCache01() {
User user = userMapper.findUserById(101);
System.out.println("第一次查询:"+user);
User user2 = userMapper.findUserById(101);
System.out.println("第二次查询:"+user2);
}
查询结果查看:
调用了两次dao查询方法,但只执行了一条sql语句,说明第二次查询获得的对象是从缓存中取的,并未执
行sql语句
测试2:测试修改后再查询
/**
* 在两次查询中间 执行 更新操作:
*/
@Test
public void testLocalCache02() {
User user = userMapper.findUserById(101);
System.out.println("-------------第一次查询:"+user);
userMapper.saveUser(new User(null,"bxg","bj","aaa"));
System.out.println("-------------更新了数据-------------");
User user2 = userMapper.findUserById(101);
System.out.println("-------------第二次查询:"+user2);
}
查询结果查看:
在一次数据库会话中,如果对数据库发生了修改操作,在修改操作后执行的相同查询,查询了数据
库,一级缓存失效。
结果:
一级缓存生命周期 当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对
象也一并释放掉 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级
缓存将不可用 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该
对象仍可使用 SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空 PerpetualCache对象的数据,但是该对象可以继续使用
\
5 二级缓存
-
概述:
一级缓存中,其最大的共享范围就是一个 SqlSession 内部;
如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。
开启二级缓存后,会使用 CachingExecutor 装饰 Executor ,进入一级缓存的查询流程前,先在
CachingExecutor 进行二级缓存的查询。 -
二级缓存示意图:
解读
开启二级缓存后,执行器Executor会被CachingExecutor包装一层。(装饰器模式) 二级缓存是手动
开启的,作用域为sessionfactory 二级缓存则是全局级别的,不同的session共用同一个二级缓存
维护二级缓存,只有在提交事务之后二级缓存才会保存 (查询的时候先走二级缓存再走一级缓存
【开启二级缓存的条件下】)
它采用的是装饰器模式它里面有个属性是 Executor delegate;(实例化哪个自己配置的,默认是
SimpleExecutor,使用有参构造给属性赋值)这里存的就是你三种执行器中的一种。
装饰者模式:在不改变原有类和继承的情况下,通过包装原对象去扩展一个新的功能。 -
开启二级缓存配置:
// MyBatis的配置文件中二级缓存: 默认已开启
<settings>
<!--默认是true protected boolean cacheEnabled = true;-->
<setting name = "cacheEnabled" value = "true" />
</settings>
//mapper映射XML中配置cache或者 cache-ref: cache标签用于声明这个namespace使用二级缓存,并
且可以自定义配置。
<cache/>
- CachingExecutor剖析:
org.apache.ibatis.executor.CachingExecutor ,实现 Executor 接口,支持二级缓存的 Executor
的实现类。
先从缓存中获取查询结果,存在就返回;
不存在,再委托给Executor delegate去数据库取;
delegate可以是SimpleExecutor、ReuseExecutor、BatchExecutor。
//query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException {
// 获得 BoundSql 对象
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建 CacheKey 对象
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 查询
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// <1> 获得 Cache 对象,即当前 MappedStatement 对象的二级缓存。
Cache cache = ms.getCache();
if (cache != null) {
// <2.1> 如果需要清空缓存,则进行清空
// 注意: 注意,此时清空的仅仅,当前事务中查询数据产生的缓存。而真正的清空,在事务的提
交时。
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) { // <2.2>
// 暂时忽略,存储过程相关
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// <2.3> 从二级缓存中,获取结果
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// <2.4.1> 如果不存在,则从数据库中查询
list = delegate.query(ms, parameterObject, rowBounds,
resultHandler, key, boundSql);
// <2.4.2> 缓存结果到二级缓存中
tcm.putObject(cache, key, list);
}
// <2.5> 如果存在,则直接返回结果
return list;
}
}
// <3> 不使用缓存,则从数据库中查询
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key,
boundSql);
}
// update
@Override
public int update(MappedStatement ms, Object parameterObject) throws
SQLException {
// 如果需要清空缓存,则进行清空
flushCacheIfRequired(ms);
// 执行 delegate 对应的方法
return delegate.update(ms, parameterObject);
}
解析:
- 获得 Cache 对象,即当前 MappedStatement 对象的二级缓存
- 如果没有 Cache 对象,说明该 MappedStatement 对象,未设置二级缓存,则调用 delegate
属性的 #query(…) 方法,直接从数据库中查询。 - 如果有 Cache 对象,说明该 MappedStatement 对象,有设置二级缓存:
- 缓存执行器流程分析:
6 小结
- 我们可以在 ExecutorType 中看到,枚举了三种类型: SIMPLE 、REUSE 、BATCH
- 如果没有指定类型,默认为 SimpleExecutor 对象
- 如果开启缓存,创建 CachingExecutor 对象,进行包装
在优化系统性能时,优化数据库性能是非常重要的一个环节,而添加缓存则是优化数据库时最有
效的手段之一。
正确、合理地使用缓存可以将一部分数据库请求拦截在缓存这一层。
MyBatis 中提供的一级缓存和二级缓存,而这两级缓存都是依赖于基础支持层中的缓存模块实现的。这里需要注意的是,在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的
,占用系统资源,并且有一定安全性问题,建议直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。