关键词:
首先需要明白,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的缓存机制源码分析之二级缓存解析(代码片段)
引言本篇源码解析基于mybatis3.5.8版本。MyBatis中的缓存指的是MyBatis在执行一次SQL查询时,在满足一定的条件下,会把这个sql和对应的查询结果缓存起来。当再次执行相同SQL语句的时候,就会直接从缓存中进行提取,... 查看详情
mybatis缓存的使用和源码分析
Mybatis缓存使用在Mybatis中缓存分为一级缓存和二级缓存,二级缓存又称为全局缓存,默认一级缓存和二级缓存都是开启的,只是二级缓存的使用需要配置才能生效,在Mybatis中一级缓存是SqlSession级别也就是会话级别的,而二级缓... 查看详情
mybatis源码分析五mybatis的缓存
五、MyBatis缓存文章目录五、MyBatis缓存缓存的概念与应用缓存的概念开发一个简单的缓存MyBatis中的缓存设计自定义一个Cache实现类MyBatis中的Cache实现类PerpetualCache装饰器CacheCache如何在MyBatis运行过程应用MyBatis缓存的二层体系一级... 查看详情
mybatis源码分析五mybatis的缓存
五、MyBatis缓存文章目录五、MyBatis缓存缓存的概念与应用缓存的概念开发一个简单的缓存MyBatis中的缓存设计自定义一个Cache实现类MyBatis中的Cache实现类PerpetualCache装饰器CacheCache如何在MyBatis运行过程应用MyBatis缓存的二层体系一级... 查看详情
通过源码分析mybatis的缓存
看了通过源码分析MyBatis的缓存这篇文章后,自己跟着源码过了一遍,对mybatis的一级缓存和二级缓存有了更清楚的认识。 一级缓存是SqlSession级别的,同一个sqlSession在第二次执行一个相同参数的select语句并且第一次执行... 查看详情
mybatis缓存专题-一文彻底搞懂mybatis一级缓存(代码片段)
...不能使用2.什么是一级缓存3.什么情况下会命中一级缓存4.Mybatis的一级缓存机制详解5.MyBatis关闭一级缓存6.Mybatis的一级缓存机制源码分析7.Mybatis的一级缓存机制源码分析图解总结8.一级缓存什么时候被清空?9.一级缓存key是什... 查看详情
mybatis缓存专题-一文彻底搞懂mybatis一级缓存(代码片段)
...不能使用2.什么是一级缓存3.什么情况下会命中一级缓存4.Mybatis的一级缓存机制详解5.MyBatis关闭一级缓存6.Mybatis的一级缓存机制源码分析7.Mybatis的一级缓存机制源码分析图解总结8.一级缓存什么时候被清空?9.一级缓存key是什... 查看详情
mybatis源码分析之06二级缓存
上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的。通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置<cacheeviction="LRU"type="qinfeng.zheng.RedisCache"/> MYBATIS源码分析之02... 查看详情
mybatis源码分析-缓存原理(代码片段)
...ache3.CacheKey4.一级缓存5.二级缓存6.总结参考序号内容链接1MyBatis源码分析-MyBatis入门https://thinkwon.blog.csdn.net/article/details/1148088522MyBatis源码分析-配置文件解析过程https://thinkwon.blog.csdn.net/article/details/1148089623MyBatis源码分析-映射文件... 查看详情
mybatis之缓存(代码片段)
目录一、简介 PerpetualCache增强的缓存功能分类二、原理1、PerpetualCache源码2、LRUCache,装饰器增强的缓存3、CacheKey4、一级缓存、二级缓存三、一级缓存访问&创建删除四、二级缓存开启命名空间划分访问&更新删除... 查看详情
源码级mybatis缓存策略(一级和二级缓存)(代码片段)
...们可以避免频繁的与数据库进行交互,进而提高响应速度MyBatis也提供了对缓存的支持,分为一级缓存和二级缓存,可以通过下图来理解:①、一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一... 查看详情
从根上理解mybatis的一级二级缓存
1\\.写在前头这篇帖子主要讲一级缓存,它的作用范围和源码分析(本来想把一二级缓存合在一起,发现太长了)2\\.准备工作2.1两个要用的实体类publicclassDepartmentpublicDepartment(Stringid)this.id=id;privateStringid;/***部门名称*/privateStringname... 查看详情
mybatis缓存之二级缓存
二级缓存(全局缓存):基于namespace级别的缓存,一个namespace对应一个二级缓存。工作机制:一个会话,查询一条数据,这条数据会放在当前会话的一级缓存中;如果会话关闭,该会话对应的一级缓存就消失了;可以使用二级缓... 查看详情
mybatis一级缓存案例演示&源码级原理探究~~~(代码片段)
文章目录简介什么是Mybatis一级缓存?案例演示基于同一个SqlSession实验基于不同SqlSession实验小结论源码探究回顾标题:Mybatis一级缓存是什么???最后总结为什么一级缓存又叫查询缓存?Java入门到就业学... 查看详情
mybatis一级缓存案例演示&源码级原理探究~~~(代码片段)
文章目录简介什么是Mybatis一级缓存?案例演示基于同一个SqlSession实验基于不同SqlSession实验小结论源码探究回顾标题:Mybatis一级缓存是什么???最后总结为什么一级缓存又叫查询缓存?Java入门到就业学... 查看详情
深入浅出mybatis的一级二级缓存机制(代码片段)
...及各种互联网高并发、高性能、高可用的解决方案。一、MyBatis缓存缓存就是内存中的数据,常常来自对数据库查询结果的保存。使用缓存,我们可以避免频繁与数据库进行交互,从而提高响应速度。MyBatis也提供了对... 查看详情
深入浅出mybatis的一级二级缓存机制(代码片段)
...及各种互联网高并发、高性能、高可用的解决方案。一、MyBatis缓存缓存就是内存中的数据,常常来自对数据库查询结果的保存。使用缓存,我们可以避免频繁与数据库进行交互,从而提高响应速度。MyBatis也提供了对... 查看详情
《深入理解mybatis原理6》mybatis的一级缓存实现详解及使用注意事项
《深入理解mybatis原理》MyBatis的一级缓存实现详解及使用注意事项0.写在前面 MyBatis是一个简单,小巧但功能非常强大的ORM开源框架,它的功能强大也体现在它的缓存机制上。MyBatis提供了一级缓存、二级缓存这两个缓存机制,... 查看详情