MyBatis一级缓存的简单剖析
来源:互联网 发布:星空卫视直播软件下载 编辑:程序博客网 时间:2024/06/04 22:13
今天说一下关于MyBatis的一级缓存~~
其实吧,MyBatis的一级缓存并没有什么好说的,因为MyBatis的一级缓存是通过sqlSession进行缓存的,但是在把MyBatis和Spring整合之后,我们会选择用mapper代理的方式进行开发,spring动态代理生成mapper代理对象。虽然我们看不到这个在内存里的代理对象长啥样,但是老前辈说了,这个按照mapper模板来生成的mapper代理对象,最后都会统一的把sqlSession关闭。你想想,sqlSession已经消亡了,通过sqlSession缓存的数据当然也没了。
那就先说一下这个一级缓存到底是个怎么回事:
首先,所谓缓存,就是将数据缓存在内存中(废话啊),当你再次需要通过一个相同的sql语句来查询的时候,他就会直接从缓存里拿数据,就不从数据库中查询了。这里得提一下,一级缓存的数据结构是一个键值对,<key,value> 其中key的值是 hashcode+sql+输入参数+输出参数,以此来保证key值的唯一性。那这个value是你从数据库中查询的结果了。
然后,当你使用sqlSession进行一次commit操作的时候,一级缓存会清空。这是为什么呢?原因很简单,是为了避免查询出脏数据。比如我们有一个id为9的帖子,如果你更新了帖子的内容的话,再使用相同的sql语句查询的时候,根据我们上边说的键值对的key值的构成,key值一定是相同的,因此会从缓存中拿到更新之前的数据。因此为了避免查询出脏数据,commit操作会清空这个sqlSession中的一级缓存。
最后,一级缓存是存在于sqlSession之中的,如果一个sqlSession消亡,那么这个sqlSession中的一级缓存也就消失了。所以说,一级缓存也是不用配置的,sqlSession自带一级缓存。
虽然这个一级缓存的存在十分鸡肋,但是为了发扬探究到底的精神,打开MyBatis源码,来简单探究一下MyBatis一级缓存的大致实现流程,同时也为我们后边的二级缓存的学习做一个铺垫。
上边也说了,这个一级缓存不用配置,是自动就存在于sqlSession之中的,那我们肯定要从sqlSession开始看起。
这是SqlSession的继承树,我们点开DefaultSqlSession
DefaultSqlSession中有很多方法,但是缓存嘛肯定是和查询有关的,我们可以看到selectOne方法和selectList方法
public <T> T selectOne(String statement) { return this.selectOne(statement, (Object)null);}public <T> T selectOne(String statement, Object parameter) { List list = this.selectList(statement, parameter); if(list.size() == 1) { return list.get(0); } else if(list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; }}
从以上代码可以看出,其实你只查询一条数据的时候,也是调用的selectList方法,只不过是返回的查询到的第一个值而已。public <E> List<E> selectList(String statement) { return this.selectList(statement, (Object)null);}public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT);}public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { List var6; try { MappedStatement e = this.configuration.getMappedStatement(statement); List result = this.executor.query(e, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); var6 = result; } catch (Exception var10) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10); } finally { ErrorContext.instance().reset(); } return var6;}
看一下selectList中有一个Statement,是不是有点眼熟?我们点看MappedStatement看看
public final class MappedStatement { private String resource; private Configuration configuration; private String id; private Integer fetchSize; private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets;
看下他的成员变量,有一个SqlSource,有一个Cache,这个SqlSource是就是你的sql语句,这个Cache就是二级缓存,当然我们先不管二级缓存。
回到selectList,发现调用了execute.query()方法,因此我们查看解释器executor的源码,并且查看它的继承树,找到query方法。
打开CachingExecutor的源码,看一下query方法。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql); return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if(cache != null) { this.flushCacheIfRequired(ms); if(ms.isUseCache() && resultHandler == null) { this.ensureNoOutParams(ms, parameterObject, boundSql); List list = (List)this.tcm.getObject(cache, key); if(list == null) { list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); this.tcm.putObject(cache, key, list); } return list; } } return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}哇,发现端倪了没!!你看有一句话是判断cache != null的,也就是说如果二级缓存为空,那么就执行一级缓存。也就是说缓存的查询顺序是先从二级缓存开始的。
如果二级缓存不为空,那就直接返回list,如果二级缓存为空,那就继续调用this.delegage.query()查询一级缓存,所以我们继续查看this.delegage.query()方法。
这个query方法是在Executor的另一个实现类BaseExecutor里的。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql); return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);}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(this.closed) { throw new ExecutorException("Executor was closed."); } else { if(this.queryStack == 0 && ms.isFlushCacheRequired()) { this.clearLocalCache(); } List list; try { ++this.queryStack; list = resultHandler == null?(List)this.localCache.getObject(key):null; if(list != null) { this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; } if(this.queryStack == 0) { Iterator i$ = this.deferredLoads.iterator(); while(i$.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next(); deferredLoad.load(); } this.deferredLoads.clear(); if(this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; }}最后关头到了。
你看到this.localCache.getObject(key)了吗!!这个key就是我们前边说到的键值,如果我们查询到这个key值是有对应的缓存的,那就直接用这个key,调用底下判断缓存不为空之后的方法this.handleLocalyCachedOutputParameters()了,直接获取到value值。如果缓存为空,则调用queryFromDataBase方法,也就是从数据库里查询数据了。
至此,一级缓存差不多就这样了。
哈哈哈,不光说一级缓存了,有没有感觉对MyBatis的理解更近一步了呢??查看源码的学习方式,是不是让你学习之后感觉更加充实了呢??
弱智死垃圾就要好好学习啦
- MyBatis一级缓存的简单剖析
- mybatis的一级缓存
- mybatis的一级缓存
- MyBatis的一级缓存
- mybatis中一级缓存和二级缓存的简单介绍
- mybatis中一级缓存和二级缓存的简单介绍
- AAA Mybatis一级缓存 简单入门(上篇)
- mybatis的缓存机制(一级缓存二级缓存)
- mybatis的一级缓存实现详解
- mybatis的一级缓存实现原理
- MyBatis一级缓存引起的无穷递归
- MyBatis的一级缓存实现详解
- mybatis的延迟加载,一级缓存,二级缓存
- Mybatis的一级缓存和二级缓存-2
- MyBatis的一级缓存和二级缓存
- Mybatis的一级缓存和二级缓存机制
- Mybatis的一级缓存和二级缓存
- Mybatis的一级缓存和二级缓存
- EJB到底是什么?
- 用户注册
- sklearn.linear_model.LinearRegression
- PYTHON数据分析入门
- Educational Codeforces Round 22 C The Tag Game(树的深度)
- MyBatis一级缓存的简单剖析
- Android中如何使按钮的背景变得透明
- 如何从CSDN上转载文章
- .NET线程同步之SpinLock构造
- Date and Time in C++
- ReactRouter升级 v2 to v4
- centos7 mysql数据库安装和配置
- BZOJ 1131 Sta
- C++可变参数列表处理宏va_list、va_start、va_end的使用