文章目录
Mybatis 缓存问题导入
在上一篇文章中提到了默认情况下,“不可重复读”的现象无法复现,当时采用了编写同名冗余 Mapper 方法的方式再现了“不可重复读”。其实这样回避了一个问题,即 Mybatis 的缓存机制。由于该机制可能导致事务设置的失效,所以这里我们对该机制进行阐述。
注意
本文所说的缓存都是 Mybatis 中的缓存。
Mubatis 的缓存分为 本地缓存(local cache)和二级缓存(second level cache)。
只有配置事务的时候,Mybatis 的缓存机制才会生效
,没有事务的时候,Mybatis 的每次搜索都会查询数据库。
如果你想亲自测试,需要将事务的隔离级别设置为读已提交
,也可以是读未提交
,但是要确保重复读时写的提交已经完成,但是绝不可以是可重复读
或者序列化
。
背景知识
缓存是针对某一个Mapper的某一个方法来说的,即只有同一个Mapper的同一个方法才会共享缓存,方法的参数不同不会共享缓存。
在进行搜索方法调用的时候,如果缓存被命中,就不会再次查询数据库。
Mybatis 的缓存分为 本地 缓存和二级缓存,只有配置事务的时候,Mybatis 的缓存机制才会生效,本地缓存默认开启,二级缓存默认关闭
。
缓存只有在重复调用的时候才有意义,本地缓存
默认是事务级别
内共享,本地缓存会被增删改、提交事务、关闭事务以及关闭 session 所清空。
Mybatis二级缓存
是所有事务
共享的,二级缓存可以委托给 redis、memcached、ehcache 等第三方。
Mybatis 的 < settings > 配置
更多Mybatis 配置信息,传送门 http://www.mybatis.org/mybatis-3/zh/configuration.html#settings
本地缓存的特性和配置 localCacheScope
- 每当一个新 session 被创建,MyBatis 就会创建一个与之相关联的本地缓存。任何在 session 执行过的查询语句本身都会被保存在本地缓存中,那么,相同的查询语句和相同的参数所产生的更改就不会二度影响数据库了
- 本地缓存会被增删改、提交事务、关闭事务以及关闭 session 所清空。
默认情况下,本地缓存数据可在整个 session 的周期内使用,这一缓存需要被用来解决循环引用错误和加快重复嵌套查询的速度,所以它可以不被禁用掉,但是你可以设置 localCacheScope=STATEMENT 表示缓存仅在语句执行时有效。- 注意,如果 localCacheScope 被设置为 SESSION,那么 MyBatis 所返回的引用将传递给保存在本地缓存里的相同对象。对返回的对象(例如 list)做出任何更新将会影响本地缓存的内容,进而影响存活在 session 生命周期中的缓存所返回的值。因此,不要对 MyBatis 所返回的对象作出更改,以防后患。
设置参数 | 描述 | 有效值 | 默认值 |
---|---|---|---|
localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 | SESSION / STATEMENT | SESSION |
这里我们先以二级缓存关闭为前提,因为当二级缓存开启的时候,情况会有些复杂。
localCacheScope 等于 SESSION 的时候,在一个事务内多次调用同一个sql查询,只会调用一次数据库。但是不会同另一个事务共享该缓存。默认值为 SESSION。
localCacheScope 等于 STATEMENT 的时候,不管是不是属于同一个事务,每一次查询都需要访问数据库。
在Mybatis 的配置文件配置 localCacheScope
```xml
<setting name="localCacheScope" value="STATEMENT" />
<setting name="localCacheScope" value="SESSION" />
```
二级缓存的配置
二级缓存的开启需要一个组合拳
- 第一在Mybatis 配置文件中
<setting name="cacheEnabled" value="true"/>
- 第二在 Mapper 文件中添加 < /cache >你可以仅添加< /cache >,也可以添加如下内容
<mapper namespace="com.bestcxx.stu.springmybatis.dao.TestTableOneMapper">
<cache eviction="LRU"
type="org.apache.ibatis.cache.impl.PerpetualCache"
flushInterval="120000"
size="1024"
readOnly="true"/>
设置参数 | 描述 | 可选值 | 默认值 |
---|---|---|---|
eviction | 缓存回收策略 | LRU-最近最少使用的;FIFO-先进先出的;SOFT-软引用,移除基于垃圾回收器状态和软引用规则的对象;WEAK-弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象。 | LRU |
flushInterval | 刷新间隔 | 任意的正整数(60601000这种形式是不允许的),单位:毫秒 | 不限时间 |
size | 缓存个数 | 任意正整数 | 1024 |
readOnly | (只)读属性,只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过反序列化),这会慢一些,但是安全。 | true/false | false |
- 本地缓存和二级缓存混合存在的情况
- 搜索将会按照二级缓存>本地缓存->数据库的顺序进行搜索。
- 对于
集合单表数据
,当其他事务导致集合Size或者集合内字段内容发生改变,本地缓存和二级缓存都将立即失效
。 - 对于
非集合单表数据
。
如果二级缓存保留的
非集合单表数据
对应的数据库数据通过事务
被更新或删除了,那么二级缓存将立即失效
。这意味着本事务或者其他事务在同一个 Mapper 的方法被调用然后对应的数据通过事务被修改后,再次调用该 Mapper 方法,将无法命中。
如果二级缓存不失效,重复查询结果总是由二级缓存提供。如果二级缓存失效,其他事务将会直接查询数据库。
如果在本事务多次查询期间二级缓存失效,本地缓存设置为 SESSION,由于本地缓存命中,所以依旧可以不会查询数据库。
如果在本事务多次查询期间二级缓存失效,本地缓存设置为 STATEMENT ,则此时本地缓存应该在第一次搜索结束后就失效,二级缓存在数据有更新操作后立即失效,在二级缓存失效之后的首次查询将会直接查询数据库。
为什么二级缓存本地缓存统统失效了?
由于二级缓存响应事务修改数据库的这个属性,这意味着在二者同时开启的时候,你可能会遇到这种情况,尽管你的事务隔离级别是读已提交或者读未提交,但是你的缓存失效了,在重复读的间隙,数据库的条件查询结果发生了数量或者内容的改变,导致查询从新导向了数据库。
1、第一次独立事务搜索访问
2、二级缓存不命中
3、本地缓存不命中
4、查询数据库,保存到二级缓存和一级缓存
5、第二次独立事务访问
6、与第一次共享二级缓存但是不共享本地缓存
7、二级缓存由于其他独立事务提交修改而失效
8、二级缓存不命中
9、一级缓存不命中
10、查询数据库
细粒度需求:不需要为这个搜索方法设置缓存 -flushCache 属性
在 Mapper 中为某一个搜索方法单独配置 flushCache 属性。
默认 flushCache=“false”,当flushCache=“true”,相当于每次搜索都清楚缓存,本地缓存和二级缓存都会失效。
<select id="selectByPrimaryKey" ··· flushCache="true">