前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mybatis缓存揭秘

Mybatis缓存揭秘

作者头像
加多
发布2018-09-06 14:38:55
9610
发布2018-09-06 14:38:55
举报

阿里巴巴长期招聘Java研发工程师p6,p7,p8等上不封顶级别,有意向的可以发简历给我,注明想去的部门和工作地点:1064454834@qq.com

欢迎关注微信公众号:技术原始积累 获取更多技术干货

一、前言

因为在做项目时候遇到了mybatis缓存的坑,所以全面学习了下mybaits的缓存知识,一来避免后面再次采坑,二来为其他童鞋提供前车之鉴。

二、Mybaits缓存作用

为了提高数据库查询性能,缓解数据查询压力,后面会具体看到一级是在sqlsession级别缓存了查询结果和二级缓存则是在namespace级别缓存了查询结果。

三、Mybaits一级缓存

3.1 问题示例

在做项目时候遇到一个问题,就是数据库里面有个任务表,单击页面上面设备测试时候,会触发一个事件,这个事件被定时钟轮询线程捕获后,修改任务的状态,而在单击设备测试的同时会有一个rpc请求去查看任务状态,这个rpc的bo层是个循环,循环查询任务状态。结果发现定时钟线程已经修改了任务状态,但是rpc的bo层循环查找的状态还是修改前面的,但是明明数据库里面状态已经修改了啊。经过断点发现,rpc的bo层循环查找的结果一直和第一次查找结果一样,好奇怪,为啥类,第一想法是不是事务隔离性问题啊,毕竟mysql默认配置的隔离水平是Repeated read,但是查看配置我用的mysql是Read Commited,那就郁闷了啊,想知道答案请看3.2

3.2一级缓存原理

Mybatis的一级缓存是SqlSession级别的,我们知道每个mapper接口对应一个SqlSession(这样说应该不准确,应该是一个线程中一个mapper接口对应一个sqlsession),所以Mybatis的一级缓存在不同mapper之间是隔离,相互不影响的。另外在执行Add,Update,Del时候,会清空当前线程SqlSession的一级缓存避免脏读。默认情况下mybaits开启一级缓存。

Mybaits一级缓存结构图:

screenshot.png

然后我们深入一个SqlSession看看它是怎么玩的?

screenshot.png

剧透下同一个mapper在第一次执行select时候会发现sqlsession缓存没有记录,会去数据库查找,然后把结果保存到缓存,第二次同等条件查询下,就会从缓存中查找到结果。另外为了避免脏读,每次执行更新新增删除时候会清空当前sqlsession缓存。

下面从代码时序图看下:

screenshot.png

代码为:

 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 (closed) {
      throw new ExecutorException("Executor was closed.");
    }
   //根据配置是否刷新缓存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
     //从缓存获取结果
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //缓存中不存在,则在数据库中查询,查询后把结果放入缓存
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

由于内部insert,update,delete最终调用的都是update方法,所以看下update代码:

public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
   //清空一级缓存
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

由于默认情况下mybatis开启一级缓存,所以如果你需要每次查询都从数据库查询,可以在mapper.xml里面具体sql语句添加flushCache="true";

  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long"  flushCache="true">
 select 
    <include refid="Base_Column_List" />
    from COMPANY
    where ID = #{id,jdbcType=NUMERIC}
    and is_deleted = 'n'
</select>

3.1节的问题通过配置这个解决。

四、Mybatis二级缓存

4.1介绍

二级缓存是namespace级别的,这个namespace就是指mapper文件里面那个namepsace,同一个namespace下的搜寻语句共享一个二级缓存。那么二级缓存是怎么样的构造那,先上个图:

screenshot.png

4.2 原理

上CachingExecutor的查询代码如下:

 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) {
      flushCacheIfRequired(ms);
      //如果配置了使用二级缓存,则从缓存中取
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
         //缓存找不到则代理给SimpleExecutor查找,
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
   //没有设置二级缓存,则直接委托给SimpleExecutor查找
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

如果开启了二级缓存,则先从二级缓存中查找,查找不到则委托为SimpleExecutor查找,而它则会先从一级缓存中查找,查找不到则从数据库查找。

五、总结

mybaits的二级缓存一般不怎么使用,默认一级缓存是开启的,如果项目中遇到数据更新后查询出来的数据却没有改变,那么可以从数据隔离性和mybaits缓存方面查找问题所在。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.05.05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、Mybaits缓存作用
  • 三、Mybaits一级缓存
    • 3.1 问题示例
      • 3.2一级缓存原理
      • 四、Mybatis二级缓存
        • 4.1介绍
          • 4.2 原理
          • 五、总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档