MyBatis-缓存

前言

最近还是在弄片刻的二次版本,在写后端服务的时候,因为考虑的比较多,一是想多学习学习其他的技术,二是想巩固现在已经会的技术,所以在写的时候束手束脚的,对于很多以前用的东西理解的都不是很透彻,导致了原本有简便的方法解决问题,现在却用了一种很麻烦的解决办法来解决问题。
所以我又给自己挖了一个坑。


简介

MyBatis 在内部提供了查询缓存的机制,用于减轻数据库压力,提高数据库性能。
MyBatis 可以提供一级缓存和二级缓存,一级缓存的主要作用域在Sqlsession,而二级缓存的作用域在Mapper 。

一级缓存的作用域在SqlSession,在操作数据库时需要构造SqlSession 对象,在对象中有一个数据结构用于存储缓存数据,不同的SqlSession 之间的缓存数据区域是互不影响。
Mybatis 默认开启一级缓存。
一级缓存在同一个SqlSession 下执行两次相同的sql 语句,第一次执行sql 语句会将从数据库查询到的数据写入到缓存中去,而第二次会直接从缓存中去取数据,不再去数据库查询数据,从而提高查询效率。
从上述德尔描述中,大致能明白在SqlSession 中进行sql 语句执行,遇到插入、更新、删除操作时,一级缓存中的数据会被删除,直到下一次查询,数据才会重新被放到缓存中。

二级缓存的作用域在Mapper ,多个SqlSession 去操作同一个Mapper 中的sql 语句,多个SqlSession 操作数据库会获得二级缓存,而SqlSession 之间是可以共享二级缓存的,也就是说二级缓存是跨SqlSession的。
MyBatis 默认不开启二级缓存。


一级缓存

在系统运行过程中,有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL 语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。

基础

每个SqlSession 中持有了Executor ,每个Executor 中有一个LocalCache 。

当用户发起查询时,MyBatis 根据当前执行的语句生成MappedStatement ,在Local Cache 进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。

如何使用MyBatis 的一级缓存。只需在MyBatis 的配置文件中,添加如下语句,就可以使用一级缓存。
共有两个选项,SESSION 或者STATEMENT ,默认是SESSION 级别,即在一个MyBatis 会话中执行的所有语句,都会共享这一个缓存。一种是STATEMENT 级别,可以理解为缓存只对当前执行的这一个Statement 有效。

1
<setting name="localCacheScope" value="SESSION"/>

命中原则

  • SqlSession 不同,由于一级缓存是基于SqlSession 级别的,所以当使用不同SqlSession 进行查询时缓存也不同。
  • SqlSession 相同,手动清空一级缓存。
  • SqlSession 相同,两次查询之间执行了增删改操作。
  • SqlSession 相同,查询条件不同。

生命周期

1.jpg

源码解读

  • SqlSession: 对外提供了用户和数据库之间交互需要的所有方法,隐藏了底层的细节。默认实现类是DefaultSqlSession 。
  • Executor: SqlSession 向用户提供操作数据库的方法,但和数据库操作有关的职责都会委托给Executor ,一级缓存主要学习BaseExecutor 的内部实现。
  • Cache: MyBatis 中的Cache 接口,提供了和缓存相关的最基本的操作。

总结

  • MyBatis 一级缓存的生命周期和SqlSession 一致。
  • MyBatis 一级缓存内部设计简单,只是一个没有容量限定的HashMap ,在缓存的功能性上有所欠缺。
  • MyBatis 的一级缓存最大范围是SqlSession 内部,有多个SqlSession 或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement 。(在这种情况下,建议使用自定义缓存)

二级缓存

在一级缓存中,其最大的共享范围就是一个SqlSession 内部,如果多个SqlSession 之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor 装饰Executor ,进入一级缓存的查询流程前,先在CachingExecutor 进行二级缓存的查询。

2.jpg

基础

二级缓存开启后,同一个namespace 下的所有操作语句,都影响着同一个Cache ,即二级缓存被多个SqlSession 共享,是一个全局的变量。

当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。

配置二级缓存:

  • 在MyBatis的配置文件中开启二级缓存。
    1
    <setting name="cacheEnabled" value="true"/>
  • 在MyBatis的映射XML中配置cache或者 cache-ref 。
    1
    <cache/>  
  • type: cache使用的类型,默认是PerpetualCache ,这在一级缓存中提到过。
  • eviction: 定义回收的策略,常见的有FIFO 、LRU 。
  • flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
  • size: 最多缓存对象的个数。
  • readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
  • blocking: 若缓存中找不到对应的key,是否会一直blocking ,直到有对应的数据进入缓存。
1
<cache-ref namespace="mapper.StudentMapper"/>

cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。

命中原则

二级缓存的命中原则基本上与一级缓存一致,但是二级缓存的基础是在SqlSessionFactory 上,而一级缓存是在SqlSession 上,这是区别点。

源码解读

MyBatis 二级缓存的工作流程和前文提到的一级缓存类似,只是在一级缓存处理前,用CachingExecutor 装饰了BaseExecutor 的子类,在委托具体职责给delegate 之前,实现了二级缓存的查询和写入功能。


自定义缓存

  • 引入Redis、 Memcache 等缓存组件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!-- spring-redis实现 -->
    <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.6.2.RELEASE</version>
    </dependency>
    <!-- redis客户端jar -->
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.8.0</version>
    </dependency>
  • 配置Redis、 Memcache 等配置文件。
    1
    2
    3
    4
    5
    6
    7
    redis.host=192.168.31.55
    redis.port=6379
    redis.pass=
    redis.maxIdle=300
    redis.maxActive=600
    redis.maxWait=1000
    redis.testOnBorrow=true
  • 扩展MyBatis 自定义的cache 接口,使用Redis、 Memchache 将其重写。
    1
    public class RedisCache implements Cache{}
  • 在mapper 文件中加入二级缓存配置。
    1
    <cache type="com.example.mybatis.cache.RedisCache"/>
  • MyBatis config 文件中开启二级缓存
    1
    2
    3
    4
    <settings>
    <!--这个配置使全局的映射器(二级缓存)启用或禁用缓存-->
    <setting name="cacheEnabled" value="true" />
    </setting>
  • 单元测试

后记

引用

https://www.imooc.com/learn/1238
https://tech.meituan.com/2018/01/19/mybatis-cache.html

总结

  • MyBatis 的二级缓存相对于一级缓存来说,实现了SqlSession 之间缓存数据的共享,同时粒度更加的细,能够到namespace 级别,通过Cache 接口实现类不同的组合,对Cache 的可控性也更强。
  • MyBatis 在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。为了解决这个问题,在使用二级缓存时,建议使用自定义缓存机制来实现二级缓存。
  • 在分布式环境下,由于默认的MyBatis Cache 实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis 的Cache 接口实现,有一定的开发成本,直接使用Redis、Memcached 等分布式缓存可能成本更低,安全性也更高。

个人备注

此博客内容均为作者学习慕课网《Mybatis缓存详解》所做笔记,侵删!
若转作其他用途,请注明来源!