首先需要明白,mybatis的一级缓存就是指SqlSession缓存,Map缓存!

通过前面的源码分析知道mybatis框架默认使用的是DefaultSqlSession,它是由DefaultSqlSessionFactory创建的,下面是源码

  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);
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();
}
}

重点关注标红的两行代码,虽然mybatis的一级缓存就是SqlSession缓存,但若是说得具体点应该是SqlSession中的executor缓存。所以,非常有必须去了解一下这个executor对象。

由上图知,Executor有好几个实现 , 这种情况下,只能debug来一探究竟了!

在额外说一点,mybatis提供了三种类型的Executor

public enum ExecutorType {
SIMPLE, REUSE, BATCH
}
SIMPLE: 最基本的
REUSE: 重复使用
BATCH: 批量操作

题外话: 责任链在工作中非常常见,但是如何建立链中executor之间前后关系,套路各有不同,mybatis框架中的这种套路值得借鉴

由上面的debug知,executor是真实类型是CachingExecutor, 而这个CachingExecutor构造器中又是SimpleExecutor(我只能说套路太多)。

所以,DefaultSqlSession中的持有的Executor就是CachingExecutor。
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
@Override
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();
}
所以,上面的SqlSession接口中这一批操作数据库的方法,真正去执行的executor是CacheExecutor, 下面看下CacheExecutor中query方法(select操作)源码
  @Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)
                throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
哈哈,CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);  这行代码一看就是知道 与缓存脱不了关系了,事实上他就是创建缓存的key

具体代码:
  @Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}

这里的delegate又是什么鬼? 哈哈,前面说了CacheExecutor的构造中传了一个SimpleExecutor ,而这个delegate 就是SimleExecutor。

所以,要看mybatis是如何创建这个缓存key还得去SimleExecutor类中一探真相。而我们去到SimleExecutor类中,发现根本就没有createCacheKey()方法嘛!

好嘛,真没有, 但是不要忘了java的特性,儿子没有找老子,在BaseExecutor很容易 就看到这个createCacheKey()方法。

 @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;
}

看起来挺复杂的,不过我们不用过多关心。再复杂不也是创建一个key嘛!生成好缓存key后,我们接着往下看

mybatis中虽然默认开启了二级缓存,但是我们并没有配置二级缓存,所以程序并不会进入到红框部分,

直接走delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

******************************************************

delegate: 真实类型SimpleExecutor

key: 就是前面生成的CacheKey对象

******************************************************

SimpleExecutor 中并没有query()方法,同样会找到它的父类BaseExecutor, 在这儿就会看到mybatis先是从本地缓存中取值 ,如果缓存中有值就返回该值 ,如果没有再查库,

源码如下:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
               CacheKey key, BoundSql boundSql) throws SQLException {
// 查询结果
List<E> list;
try {
// 根据生成缓存key从localCache中取值
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else { // 如果缓存中查询为null,则从数据库中查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
}
return list;
}

接着再看queryFromDatabase()方法

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
                        CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 1. 先往缓存localCache put一个EXECUTION_PLACEHOLDER,这是啥鬼哟???
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 2. 查库,具体逻辑由子类实现,即SimpleExecutor
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 3. 又将key从 localCache 中移除了, 这是为啥呀???
localCache.removeObject(key);
}
//4. 将查库结果又put到localCache中
localCache.putObject(key, list); return list;
}

mybatis框架就是这样使用一级缓存的,下面再来看看这个localCache到底是啥鬼?

 protected PerpetualCache localCache;

就是这个对象!!!

public class PerpetualCache implements Cache {
private Map<Object, Object> cache = new HashMap<>(); @Override
public void putObject(Object key, Object value) {
cache.put(key, value);
} @Override
public Object getObject(Object key) {
return cache.get(key);
} @Override
public Object removeObject(Object key) {
return cache.remove(key);
} @Override
public void clear() {
cache.clear();
}
}

嗯, 由上面的源码可以看出来,真正是缓存在PerpetualCache 类中的HashMap中的。

*****************************************************************************************

这里有点小技巧呀,mybatis单独将这个缓存Map抽离成一个类PerpetualCache,

如果换成我们呢,大概是直接在BaseExecutor类中写个成员Map吧!!!

******************************************************************************************

BaseExecutor类中的update()方法

  public int update(MappedStatement ms, Object parameter) throws SQLException {
// 清理localCache缓存
clearLocalCache();
return doUpdate(ms, parameter);
}

别问insert,delete方法是怎么处理缓存的,看源码就知道 ,这两种方法在Executor层面都转换成了update操作!

最后,由于mybatis一级缓存是jvm级别的,所以,在集群情况下,可能会出现数据不同步的情况,这时可以使用SqlSession#clearCache()清除缓存,每次查询都查库;

而且,由上面的源码分析也可以知道 ,如果有配置二级缓存,一级缓存就会失效,而我们完全可以使用redis来做二级缓存,这样就完美的避免了使用一级缓存数据不同步的问题!

mybatis源码分析之05一级缓存的更多相关文章

  1. Mybatis源码分析之Cache一级缓存原理(四)

    之前的文章我已经基本讲解到了SqlSessionFactory.SqlSession.Excutor以及Mpper执行SQL过程,下面我来了解下myabtis的缓存, 它的缓存分为一级缓存和二级缓存, ...

  2. Mybatis源码分析之Cache二级缓存原理 (五)

    一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...

  3. mybatis源码分析之06二级缓存

    上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的. 通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置 &l ...

  4. mybatis源码分析(7)-----缓存Cache(一级缓存,二级缓存)

    写在前面  MyBatis 提供查询缓存,用于减轻数据库压力,提高数据库性能. MyBatis缓存分为一级缓存和二级缓存. 通过对于Executor 的设计.也可以发现MyBatis的缓存机制(采用模 ...

  5. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  6. MyBatis源码分析(4)—— Cache构建以及应用

    @(MyBatis)[Cache] MyBatis源码分析--Cache构建以及应用 SqlSession使用缓存流程 如果开启了二级缓存,而Executor会使用CachingExecutor来装饰 ...

  7. MyBatis源码分析-SQL语句执行的完整流程

    MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...

  8. Mybatis源码分析-BaseExecutor

    根据前文Mybatis源码分析-SqlSessionTemplate的简单分析,对于SqlSession的CURD操作都需要经过Executor接口的update/query方法,本文将分析下Base ...

  9. MyBatis 源码分析系列文章合集

    1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ...

随机推荐

  1. (转)k8s集群部署二:flannel网络

    转:https://blog.csdn.net/sinat_35930259/article/details/79946146 Overlay Network模式 覆盖网络,在基础网络上叠加的一种虚拟 ...

  2. thinkphp 带检索参数分页

    模板文件中,提交搜索内容要用get方法,同时设置g.m.c隐藏域. <form action="{:U('Sck/qbscHjsc')}" method="get& ...

  3. E:\Postgresql\pgAdmin4_binaryPath

    e Path to the directory containing the EDB Advanced Server utility programs (pg_dump, pg_restore etc ...

  4. Windows 下手工搭建 LNMP 环境

    参考资料 如果想方便,可以直接使用集成的 LNMP 环境(例如 PHPStudy). 下载 PHP 下载地址:https://windows.php.net/download 根据你的系统选择 32 ...

  5. 题解1433. 数码问题 (Standard IO)

    Description Alice有一个N*N的格子,把1-N^2按照从上到下从左到右的顺序填进表格中,允许在表格上进行两种操作: (1) 旋转行——这一行的数向右移动一个位置,而最后一列的数会移到第 ...

  6. Sublime Text3安装配置

    Sublime Text3的所有package都可在如下网站检索 https://packagecontrol.io/browse 以下是我的插件列表 CoolFormat 一款C\C++\C#\CS ...

  7. Rsync+inotify搭建使用

    ## Rsync搭建 ### 1.1 环境准备 ``` Rsync-Server 192.168.1.174 Client-Rsync 192.168.1.173 服务启动用户都是root,客户端的用 ...

  8. SQL常用语句之数据库数据类型-篇幅2

    系统数据类型: 1.二进制数据类型      2.整数数据类型 3.浮点数据类型         4.精确小数数据类型 5.货币数据类型         6.日期/时间数据类型 7.字符数据类型    ...

  9. python 字符编码问题总结

    都是计算机存储是二进制0101之类的数字 最早计算机在美国开始的 所以数字和英文之类的占用八位 2的8次方 256可以存储对于英文和数字戳戳有余  每个国家都有自己的编码 中国 gb2312 gbk ...

  10. bzoj1897. tank 坦克游戏(决策单调性分治)

    题目描述 有这样一款新的坦克游戏.在游戏中,你将操纵一辆坦克,在一个N×M的区域中完成一项任务.在此的区域中,将会有许多可攻击的目标,而你每摧毁这样的一个目标,就将获得与目标价值相等的分数.只有获得了 ...