简介
说明
Mybatis的缓存,包括一级缓存和二级缓存。
一级缓存的作用域是一个sqlsession内;二级缓存作用域是针对mapper进行缓存。
实际上,在开发过程中根本不会用到Mybatis的这两个缓存。因为这两个都不支持分布式,如果想用缓存,那么直接用Redis的功能就好了呀。虽然二级缓存可以使用MemCache、Ehcache、Redis等可以做到支持分布式,但不如直接使用Redis方便。
一级缓存
概述
简介
开启一级缓存后:在参数和SQL完全一样的情况下,同一个SqlSession对象调用一个Mapper方法,只执行一次SQL。因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
一级缓存的缺点
- 多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据。
- MyBatis 一级缓存内部设计简单,只是一个没有容量限定的 HashMap,在缓存的功能性上有所欠缺
一级缓存命中条件
- 相同的 sql 和参数
- 会话(Session)级别缓存,必须是相同的会话
- 相同的方法
- 相同的 namespce (mapper)
一级缓存清除场景
- 增删改操作后,执行了commit
- 关闭了sqlSession
- 调用了clearCache(),或者查询语句中包含了flush
启用与关闭
MyBatis中一级缓存默认是开启的。
关闭单个SQL的一级缓存的方法:将flushCache属性设置为true。示例如下
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap" flushCache="true">
select
<include refid="Base_Column_List" />
from cbondissuer
where OBJECT_ID = #{objectId,jdbcType=VARCHAR}
</select>
关闭所有SQL的一级缓存的方法:在MyBatis的主配置文件中,关闭所有的一级缓存:设置localCacheScope。示例如下:
<setting name="localCacheScope" value="SESSION"/>
- value的值
- SESSION:在一个 MyBatis 会话中执行的所有语句,都共享这一个缓存
- STATEMENT:缓存只对当前执行的这一个 Statement 有效
详述
每个SqlSession 中持有 Executor,每个 Executor 中有一个 LocalCache。当用户发起查询时,MyBatis 根据当前执行的语句生成 MappedStatement,在 LocalCache 进行查询,如果缓存命中,就直接返回给用户,如果缓存没有命中的话,就另外查询数据库,将结果写入 LocalCahe,最后将结果返回给用户。
一级缓存的获取流程
- 对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;
- 判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;
- 如果命中,则直接将缓存结果返回;
- 如果没命中:
- 去数据库中查询数据,得到查询结果;
- 将key和查询到的结果分别作为key,value对存储到Cache中;
- 将查询结果返回;
- 结束。
缓存的key的确定
Cache最核心的实现其实就是一个Map,将本次查询使用的特征值作为key,将查询结果作为value存储到Map中。
现在最核心的问题出现了:怎样来确定一次查询的特征值?
换句话说就是:怎样判断某两次查询是完全相同的查询?
也可以这样说:如何确定Cache中的key值?
MyBatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询:
- 传入的statementId
- 查询时要求的结果集中的结果范围(结果的范围通过rowBounds.offset和rowBounds.limit表示);
- 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() )
- 传递给java.sql.Statement要设置的参数值
MyBatis认为的完全相同的查询,不是指使用sqlSession查询时传递给算起来Session的所有参数值完完全全相同,你只要保证statementId,rowBounds,最后生成的SQL语句,以及这个SQL语句所需要的参数完全一致就可以了。
性能分析
1.MyBatis 对会话(Session)级别的一级缓存设计的比较简单,简单使用 HashMap 来维护,并没有对其容量大小进行限制
如果一直使用某一个 SqlSession 对象查询数据,是否会导致 HashMap 太大而导致 java.lang.OutOfMemoryError 错误?
- 一般而言 SqlSession 的生存时间很短,一般情况下使用一个 SqlSession 对象执行的操作不会太多,执行完就会消亡
- 对于某一个 Sqlsession 对象而言,只要执行 update 操作(update、insert、delete),都会将这个 SqlSession 对象中对应的一级缓存清空,所以一般不会出现缓存过大,影响 JVM 内存空间的问题
- 也可以手动地释放掉 SqlSession 对象中的缓存(clearCache()、flush…)
.
2. 一级缓存是一个粗粒度的缓存,没有更新缓存和缓存过期的概念
MyBatis 的一级缓存使用了简单的 HashMap,MyBatis 只负责将查询数据库的结果存到缓存中去,不会去判断缓存存放的时间是否过长,是否过期,因此也就没有对缓存结果进行更新的说法。
3.注意点
对于数据变化频率大,并且需要高时效准确性的数据要求,我们使用 SqlSession 查询的时候,要控制好 SqlSession 的生存时间,SqlSession 的生存时间越长,他缓存的数据可能越旧,从而造成和真实数据库的误差;同时对于这种情况用户可以手动适当的情况SqlSession中的缓存
对于只执行、并且频繁执行大范围的 select 操作的 SqlSession 对象,SqlSession 对象的生存时间不宜过长
简要流程
详细流程
二级缓存
简述
说明
一级缓存中,其最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。
MyBatis的二级缓存实现可以有很多种,可以是MemCache、Ehcache、Redis等,但是需要额外的Jar包。
开启二级缓存后,查询的逻辑是:二级缓存-=> 一级缓存=> 数据库;
二级缓存的缺点
- 一个Spring里边只有一个SQLSession,所以根本用不到二级缓存
- 二级缓存默认是不支持分布式的。虽然可以用MemCache、Ehcache、Redis等实现方式来做,但生产中,没人会这么做吧。
二级缓存命中条件
相同的方法
相同的 namespace
开启与关闭
二级缓存默认是不开启的,需要手动开启二级缓存,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。
开启方式
第一步:在配置文件中 开启二级缓存的总开关
<setting name="cacheEnabled" value="true" />
第二步:mapper映射文件中开启二级缓存
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
参数名 | 属性 |
eviction | 收回策略 |
flushInterval | 刷新间隔 |
size | 引用数目 |
readOnly | 只读 |
关于eviction的各个参数属性:
参数名 | 属性 |
eviction="LRU" | 最近最少使用的:移除最长时间不被使用的对象。 (默认) |
eviction="FIFO" | 先进先出:按对象进入缓存的顺序来移除它们。 |
eviction="SOFT" | 软引用:移除基于垃圾回收器状态和软引用规则的对象。 |
eviction="WEAK" | 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。 |
第三步:实体类实现Serializable
详述
简要流程
详细流程
Spring+Mybatis的缓存失效
简介
- 在未开启事物的情况之下,每次查询,spring都会关闭旧的sqlSession而创建新的sqlSession,因此此时一级缓存是没有起作用的。
- 在开启事物的情况之下,Spring使用threadLocal获取当前资源绑定同一个sqlSession,因此此时一级缓存是有效的。