美文网首页
Mybatis的缓存

Mybatis的缓存

作者: 名字是乱打的 | 来源:发表于2020-03-11 16:39 被阅读0次

    一 Mybatis缓存体系图

    Mybatis缓存的基础实现是perpetualCache,但是mybatis利用装饰者模式对基础cache提供了许多的增强功能,比如上图,BlockingCache里利用concurrentHashMap封装了一些可重入锁Reetranlock实现了并发问题的解决

    /**
     * Simple blocking decorator
     *
     * Simple and inefficient version of EhCache's BlockingCache decorator.
     * It sets a lock over a cache key when the element is not found in cache.
     * This way, other threads will wait until this element is filled instead of hitting the database.
     *
     * @author Eduardo Macarron
     *
     */
    *当在缓存中找不到元素时,它设置对缓存键的锁定。
    *这样,其他线程将等待此元素被填充,而不是命中数据库。
    *锁acquire和release详情请看源码.
    

    一 . 一级缓存的工作位置和维护对象

    一级缓存的作用域是sqlsession,而且根据下图,查看一下sqlsession的实现类可以发现configuration是我们加载xml文件的全局变量,肯定不是sqlsess的工作位置,那么只有executor了

    而且作为Executor 我们这里有simple reuser batch Executor三种,他们都继承了BaseExecutor
    看下baseExecutor果然发现里面有个perpetualCache作为一级缓存,所以我们也称一级缓存为本地缓存,因为我们每连接一次数据库就会创建一个会话,每创建一个会员就会创建一个执行器,每个执行器里就有一个一级缓存.

    我们用户去查询数据时候会先到一级缓存中尝试获取数据,如果有数据会直接返回不在查库,如果没找到数据会先返回应用再写入缓存,如下.
    测试一级缓存

    1、在同一个 session 中共享

    BlogMapper mapper = session.getMapper(BlogMapper.class); 
    System.out.println(mapper.selectBlog(1)); 
    System.out.println(mapper.selectBlog(1));
    

    2、不同 session 不能共享

    SqlSession session1 = sqlSessionFactory.openSession(); 
    BlogMapper mapper1 = session1.getMapper(BlogMapper.class); 
    System.out.println(mapper.selectBlog(1));
    

    PS:一级缓存在 BaseExecutor 的 query()——queryFromDatabase()中存入。在 queryFromDatabase()之前会 get()。


    3、同一个会话中,update(包括 delete)增删改会导致一级缓存被清空

    测试代码.
    mapper.updateByPrimaryKey(blog); 
    session.commit();
    System.out.println(mapper.selectBlogById(1));
    

    一级缓存是在 BaseExecutor 中的 update()方法中调用 clearLocalCache()清空的 (无条件),query 中会判断。
    为什么呢?
    如下图所示,我们的mapper元素属性中有个flushCache,在增删改里他是开启的true,在查询select里它是关闭的.这个会刷新该会话的缓存


    4、其他会话更新了数据,导致读取到脏数据(一级缓存不能跨会话共享)
    //会话 2 更新了数据,会话 2 的一级缓存更新
    BlogMapper mapper2 = session2.getMapper(BlogMapper.class); 
    mapper2.updateByPrimaryKey(blog); 
    session2.commit();
    // 会话 1 读取到脏数据,因为一级缓存不能跨会话共享 System.out.println(mapper1.selectBlog(1));
    

    一级缓存的不足

    使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据 可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题。如果要 解决这个问题,就要用到二级缓存。

    如何关闭一级缓存呢?
    方法:在配置文件的setings中更改localCacheScope属性值为STATEMENT
    原理: 原理解释来自mybatis3官方文档

    二 . 二级缓存

    二级缓存是用来解决一级缓存不能跨会话共享的问题的,\color{red}{范围是 namespace 级别 }的,其实也就是\color{red}{同一个接口}可以被多个 SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享), 生命周期和应用同步。

    如果开启了二级缓存,二级缓存应该是工作在一级缓存之前,还是 在一级缓存之后呢?二级缓存是在哪里维护的呢?

    二级缓存应该是工作在一级缓存之前.(如果二级缓存中有就会直接返回,如果二级缓存没有,会去一级缓存中查,一级缓存也没有会去datasource中查,并依次存储,详情可以看后面有个流程图)

    要跨会话共享的话,SqlSession 本 身和它里面的 BaseExecutor 已经满足不了需求了,那我们应该在 BaseExecutor 之外创建一个对象。实际上我们的二级缓存还是利用的装饰者模式做了一个包装类cachingExecutor对一级缓存做了增强,如果启用了 二级缓存,MyBatis 在创建 Executor 对象的时候会对 Executor 进行装饰。

    TransactionalCacheManager

    CachingExecutor 对于查询请求,会判断二级缓存是否有缓存结果,如果有就直接 返回,如果没有委派交给真正的查询器 Executor 实现类,比如 SimpleExecutor 来执行 查询,再走到一级缓存的流程。最后会把结果缓存起来,并且返回给用户.

    开启二级缓存的方法

    第一步:在 mybatis-config.xml 中配置了(可以不配置,默认是 true):

    <setting name="cacheEnabled" value="true"/> 
    

    只要没有显式地设置cacheEnabled=false,都会用 CachingExecutor 装饰基本的 执行器
    第二步:在 Mapper.xml 中配置<cache/>标签:

    <!-- 声明这个 namespace 使用二级缓存 --> 
    <cache type="org.apache.ibatis.cache.impl.PerpetualCache"
    size="1024" <!—最多缓存对象个数,默认 1024-->
    eviction="LRU" <!—回收,淘汰策略--> 
    flushInterval="120000" <!—自动刷新时间 ms,多久自动刷新一次二级缓存,未配置时只有调用时刷新--> 
    readOnly="false"/> <!—默认是 false(安全),改为 true 可读写时,对象必须支持序列化 -->
    

    第三步:确保要使用缓存的select语句没有关闭缓存
    我们mapper.xml文件里面的元素有个属性 usecache="",默认=ture,如果你添加了usecache="false",那么他就不会走缓存了
    cache 属性详解:


    Mapper.xml 配置了<cache>之后,select()会被缓存。update()、delete()、insert() 会刷新缓存。

    我们配置二级缓存后,内部会通过一个CacheingExecutor对原来的Executor进行一个装饰,这样如果我们二级缓存中有数据就会直接返回,如果


    二级缓存工作流程以及原理

    思考:如果 cacheEnabled=true,Mapper.xml 没有配置标签,还有二级缓存吗? 还会出现 CachingExecutor 包装对象吗?
    会.

    只要 cacheEnabled=true 基本执行器就会被装饰。有没有配置<cache>,决定了在 启动的时候会不会创建这个 mapper 的 Cache 对象,最终会影响到 CachingExecutor query 方法里面的判断:
    if (cache != null) { }

    思考:如果某些查询方法对数据的实时性要求很高,不需要二级缓存,怎么办?

    我们可以在单个 Statement ID 上显式关闭二级缓存(默认是 true)
    <select id="selectBlog" resultMap="BaseResultMap" useCache="false">

    思考:思考:为什么事务不提交,二级缓存不生效?

    因为二级缓存使用 TransactionalCacheManager(TCM)来管理,最后又调用了 TransactionalCache 的getObject()、putObject和 commit()方法,
    TransactionalCache 里面又持有了真正的 Cache 对象,比如是经过层层装饰的 PerpetualCache。
    \color{red}{在 putObject 的时候,只是添加到了 entriesToAddOnCommit 里面,只有它的}
    \color{red}{ commit()方法被调用的时候才会调用 flushPendingEntries()真正写入缓存。}
    \color{red}{它就是在 DefaultSqlSession 调用 commit()的时候被调用的}

    思考:为什么增删改操作会清空缓存?

    CachingExecutor 的 update()方法里面会调用 flushCacheIfRequired(ms)isFlushCacheRequired 就是从标签里面渠道的 flushCache的值。
    而增删改操作的 flushCache 属性默认为 true。

    三 . 什么时候开启二级缓存?

    一级缓存默认是打开的,二级缓存需要配置才可以开启。那么我们必须思考一个问 题,在什么情况下才有必要去开启二级缓存?

    • 1、因为所有的增删改都会刷新二级缓存,导致二级缓存失效,所以少在增删改为主的语句中使用\color{red}{二级缓存更适合在查询为主的应用中使用},比如历史交易、历史订单的查询。否则缓存就失去了意义。

    • 2、如果多个 namespace 中有针对于同一个表的操作,比如 Blog 表,如果在一个 namespace 中刷新了缓存,另一个 namespace 中没有刷新,使用一级缓存就会出现读到脏数据的情况。所以,推荐在一个 Mapper 里面只操作单表的情况使用。,如果是多个mapper共同协作那么势必会出现不同namespace,这就使得我们的二级缓存不能跨namespace存取数据,可能会出现另一种如一级缓存的脏数据问题.

    四 . 那么如何解决二级缓存的作用范围还是比较窄,会出现多个mapper之间的脏数据问题呢?
    第三方缓存做二级缓存

    除了 MyBatis 自带的二级缓存之外,我们也可以通过实现 Cache 接口来自定义二级 缓存。MyBatis 官方提供了一些第三方缓存集成方式,比如 ehcache 和 redis: https://github.com/mybatis/redis-cache pom 文件引入依赖
    1.pom 文件引入依赖:

    <dependency> 
    <groupId>org.mybatis.caches</groupId> 
    <artifactId>mybatis-redis</artifactId>
    <version>1.0.0-beta2</version>
     </dependency>
    

    2.Mapper.xml 配置,type 使用 RedisCache:

    <cache type="org.mybatis.caches.redis.RedisCache"
     eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
    

    3.redis.properties 配置:

    host=localhost 
    port=6379 
    connectionTimeout=5000 
    soTimeout=5000 
    database=0
    

    当然,我们也可以使用独立的缓存服务,不使用 MyBatis 自带的二级缓存。


    说到这里大部分人都会觉得真麻烦....真鸡肋...
    实际上确实如此
    生产中我们绝大多数时候还是直接用如redis的第三方缓存库,直接专门做的缓存.

    相关文章

      网友评论

          本文标题:Mybatis的缓存

          本文链接:https://www.haomeiwen.com/subject/dfandhtx.html