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;    }}
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方法,只不过是返回的查询到的第一个值而已。

看一下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的理解更近一步了呢??查看源码的学习方式,是不是让你学习之后感觉更加充实了呢??

弱智死垃圾就要好好学习啦

原创粉丝点击