mybatis一级缓存及源码分析

314 阅读3分钟

一、一级缓存的介绍

一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数 据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的

二、一级缓存的执行过程

1、第一次发起查询用户信息,会先去找缓存中是否有id为1的用户信息,如果没有,从 数据

库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。

2、 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的 一级 缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。

(使用sqlSesion.clearCache()也可以手动刷新一级缓存)

3、 第二次发起查询用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息

三、一级缓存的结构

statementid:namespace+id

parmas:方法参数

boundSql:执行sql语句

rowBounds:分页参数

四、从sqlsession开始查找一级缓存

1.一级缓存的底层结构

查看sqlSession类下面的所有方法,发现只有方法名为 clearCache()的方法和缓存沾点关系 。

从此方法入手,查看其父类,得出以下关系:

不支持在 Docs 外粘贴 block

其中,在PerpetualCache中可以看出 ,最终使用了一个 cache.clear()方法 。而这里的cache是一个HashMap,说明一级缓存的底层为一个HashMap

2.一级缓存的创建

通过第一步的查找,跟踪清除缓存流程,得到的一级缓存的结构。但是一级缓存在哪里创建的呢?

我觉得是Executor,为什么这么认为?因为Executor是 执 行器,用来执行SQL请求,而且清除缓存的方法也在Executor中执行,所以很可能缓存的创建也很 有可 能在Executor中,看了一圈发现Executor中有一个createCacheKey方法,这个方法很像是创 建缓存的 方法,createCacheKey方法由BaseExecutor继承,代码如下

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {

    if (this.closed) {

        throw new ExecutorException("Executor was closed.");

    } else {

              //MappedStatement 的 id

         // id就是Sql语句的所在位置包名+类名+ SQL名称 

         cacheKey.update(ms.getId());

         // offset 就是 0

         cacheKey.update(rowBounds.getOffset());

         // limit 就是 Integer.MAXVALUE 

         cacheKey.update(rowBounds.getLimit());

         //具体的SQL语句

         cacheKey.update(boundSql.getSql());

         //后面是update 了 sql中带的参数

         cacheKey.update(value);

         

     ......



        if (this.configuration.getEnvironment() != null) {

        //把key和value存进去

            cacheKey.update(this.configuration.getEnvironment().getId());

        }



        return cacheKey;

    }

}

这里需要注意一下最后一个值,configuration.getEnvironment().getId()这是什么,这其实就是 定义在 mybatis-config.xml中的标签,见如下。

 <environments default="development">

 <environment id="development">

 <transactionManager type="JDBC"/>

 <dataSource type="POOLED">

     <property name="driver" value="${jdbc.driver}"/>

    <property name="url" value="${jdbc.url}"/>

    <property name="username" value="${jdbc.username}"/>

    <property name="password" value="${jdbc.password}"/>

    </dataSource>

  </environment>

</environments>

 

 

 

 

update方法:

public void update(Object object) {

    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);

    ++this.count;

    this.checksum += (long)baseHashCode;

    baseHashCode *= this.count;

    this.hashcode = this.multiplier * this.hashcode + baseHashCode;

    this.updateList.add(object);

}

创建缓存key会经过一系列的update方法,update方法由一个CacheKey这个对象来执行的,这个 update方法最终由updateList的list来把五个值存进去,对照上面的代码和下面的图示,你应该能 理解 这五个值都是什么了

3.一级缓存的使用

一级缓存在查询中使用,那我们看下BaseExecutor的query方法:

 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {

    BoundSql boundSql = ms.getBoundSql(parameter);

   //此处调用上面说的createCacheKey创建缓存方法

    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 {

       ......

        try {

            ++this.queryStack;

            //是否有缓存

            list = resultHandler == null ? (List)this.localCache.getObject(key) : null;

            

            if (list != null) {

            //若有缓存,从缓存中取值

                this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

            } else {

            //若无缓存,调用queryFromDatabase

                list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

            }

       .....

}

queryFromDatabase:

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

    this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

    List list;

    try {

    //从数据库查询

        list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

    } finally {

        this.localCache.removeObject(key);

    }

    //查询结果存入缓存中

    this.localCache.putObject(key, list);

    if (ms.getStatementType() == StatementType.CALLABLE) {

        this.localOutputParameterCache.putObject(key, parameter);

    }



    return list;

}