zoukankan      html  css  js  c++  java
  • Mybatis 缓存分析

      其实本来不想专门的写一篇关于mybatis缓存的博客的。在之前的博客中已经大致的把mybatis的整体流程讲了一遍。只要按照步骤一步步的点进去,关于缓存的代码很容易就能发现。但是今天在看代码的时候突然对mybatis在缓存的设计上有点疑惑,花了点时间把它搞懂了,同时发现网上没有专门对这块作分析的,所以还是很有必要写出来和大家分享下。

    缓存概述


    mybatis的缓存分为一级缓存和二级缓存。一级缓存作用于sqlssesion,即一级缓存的生命周期只是在一个sql回话之内。二级缓存作用于namespace,生命周期可以看做是程序运行的生命周期。

    mybatis默认不启用二级缓存,当启用二级缓存时,mybatis先去查询二级缓存,再查询一级缓存,最后才是数据库。

    一级缓存


     

     这里提到一个概念就是sqlsession,sqlsession就是一个sql回话,一个sql回话代表了一个sql连接数据库,执行,到最终commit的操作。下面请看一组测试代码

    public Alarm test3() {
            Alarm alarm = new Alarm();
            alarm = alarmMapper.selectList(new EntityWrapper<Alarm>().eq("levels", 0)).get(0);
            alarm = alarmMapper.selectList(new EntityWrapper<Alarm>().eq("levels", 0)).get(0);
            return alarm;
        }

    大家觉得上述查询会用到一级缓存么

    答案是不能,因为上述两个查询分别会创建两个sqlssesion。那我们怎么做才能共享sqlssesion呢?很简单,放到一个事物即可

    @Transactional
        public Alarm test3() {
            Alarm alarm = new Alarm();
            alarm = alarmMapper.selectList(new EntityWrapper<Alarm>().eq("levels", 0)).get(0);
            alarm = alarmMapper.selectList(new EntityWrapper<Alarm>().eq("levels", 0)).get(0);
            return alarm;
        }

    每个Sqlsseion创建的同时会伴随创建一个executor

    每个executor里面都会有localCache

    每次的查询缓存步骤如下图所示

    上述就是mybatis的一级缓存的流程。通过上文我们知道一级缓存是由localcache储藏的。localcache实质上就是个Hashmap, mybatis将查询的结果当做localcache的value值,那么localcache的key又是什么呢?

    CacheKey


     接着上文的问题,localcache的key是什么呢?换言之,怎样判断某两次查询是完全相同的查询呢。

    MyBatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询:

    1. 传入的 statementId 
    
    2. 查询时要求的结果集中的结果范围 (结果的范围通过rowBounds.offset和rowBounds.limit表示);
    
    3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )
    
    4. 传递给java.sql.Statement要设置的参数值

    后三条都好理解,第一条的statementId是是什么呢。如下图所示

    statementId就是namespace+mapperId

    接下开看下mybatis对Cachekey的定义

    Cachekey的构造函数: 

    这些参数什么意思我们等会再说。我们看下Cachekey的创建过程。

     

    在进行真正的查询之前,先创建出cachekey

    我们再看下update方法

     每增加一个条件,hashcode就会变化一次。这也能保证每个Cachekey的hashcode不一样。

    二级缓存


     二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

    我们先来看下每次查询时二级缓存是怎么实现的

    1.创建sqlsession

     2.得到CacheExecutor

    这里用到了一个装饰模式

    我们来看具体的查询

    这里的tcm就是TransactionalCacheManager

    所以实际上TransactionalCacheManager的数据结构就是一个大Map里面的value也是个map。大map的key是Cache对象,小map的key就是cachekey。

    我们再仔细的看下缓存的put方法

    第一步

     

    第二步,点进去

    第三步

    第四步

    第三步中的疑问正是我之前不解的地方。解答的关键还是在cache对象上。

    首先cache是通过Mapperstatement而产生的

    Mapperstatement是存储在Configuration中,二级缓存和Configuration的生命周期相同,都是一整个应用的生命周期。所以这个时候推测,二级缓存实际上存放在Mapperstatement中的Cache中

    那么问题又来了,二级缓存明明是存在TransactionalCacheManager中的,是怎么存到cache里面的呢。

    这里就涉及到了二级缓存的另外一个特性了

    只有SqlSession commit或close之后,二级缓存才会生效
    

     我们来看代码TransactionalCache的cmmit方法

    同时在TransactionalCache还有个内部类

    这时候我们往上看第四步,实际上我们往TransactionalCacheManager这个大map里面存的value值就是个Addentry,这里的cache就是由MapperStatement传进来的。在最后commit的时候会把缓存值保存到cache中。这也是为什么我们还有在commit或者close的时候,二级缓存才能生效。

    为什么还有close呢,

    close前需要commit啊!

    以上就是对mybatis缓存的一些理解。本文并没有对mybatis做出详细的描述,只是针对二级缓存的生命周期做了些研究。

    参考


    https://blog.csdn.net/luanlouis/article/details/41280959

    https://tech.meituan.com/mybatis_cache.html

    转载请标注来源: https://www.cnblogs.com/xmzJava/p/9096722.html

  • 相关阅读:
    个人7天安排
    七天安排
    京东搜索规则
    关于从一个整数数组中求得最大的子整数组和
    结对项目开发--电梯调度
    分析英文文本各个词出现的频率
    关于安卓版的eclipse连接数据库并誓言增删改查
    电梯调度 结对开发项目
    求一个二维数组的最大子数组
    求一堆数组中最大的子数组
  • 原文地址:https://www.cnblogs.com/xmzJava/p/9096722.html
Copyright © 2011-2022 走看看