所有分类
  • 所有分类
  • 未分类

MyBatis原理–缓存机制

简介

说明

Mybatis的缓存,包括一级缓存和二级缓存。

一级缓存的作用域是一个sqlsession内;二级缓存作用域是针对mapper进行缓存。

实际上,在开发过程中根本不会用到Mybatis的这两个缓存。因为这两个都不支持分布式,如果想用缓存,那么直接用Redis的功能就好了呀。虽然二级缓存可以使用MemCache、Ehcache、Redis等可以做到支持分布式,但不如直接使用Redis方便。

一级缓存

概述

简介

开启一级缓存后:在参数和SQL完全一样的情况下,同一个SqlSession对象调用一个Mapper方法,只执行一次SQL。因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。

一级缓存的缺点

  1. 多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据。
  2. MyBatis 一级缓存内部设计简单,只是一个没有容量限定的 HashMap,在缓存的功能性上有所欠缺

一级缓存命中条件

  1. 相同的 sql 和参数
  2. 会话(Session)级别缓存,必须是相同的会话
  3. 相同的方法
  4. 相同的 namespce (mapper)

一级缓存清除场景

  1. 增删改操作后,执行了commit
  2. 关闭了sqlSession
  3. 调用了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"/>
  1. value的值
    1. SESSION:在一个 MyBatis 会话中执行的所有语句,都共享这一个缓存
    2. STATEMENT:缓存只对当前执行的这一个 Statement 有效

详述

每个SqlSession 中持有 Executor,每个 Executor 中有一个 LocalCache。当用户发起查询时,MyBatis 根据当前执行的语句生成 MappedStatement,在 LocalCache 进行查询,如果缓存命中,就直接返回给用户,如果缓存没有命中的话,就另外查询数据库,将结果写入 LocalCahe,最后将结果返回给用户。

一级缓存的获取流程

  1. 对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;
  2. 判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;
    1. 如果命中,则直接将缓存结果返回;
    2. 如果没命中:
      1. 去数据库中查询数据,得到查询结果;
      2. 将key和查询到的结果分别作为key,value对存储到Cache中;
  3. 将查询结果返回;
  4. 结束。

缓存的key的确定

Cache最核心的实现其实就是一个Map,将本次查询使用的特征值作为key,将查询结果作为value存储到Map中。

现在最核心的问题出现了:怎样来确定一次查询的特征值?
换句话说就是:怎样判断某两次查询是完全相同的查询?
也可以这样说:如何确定Cache中的key值?

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

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

MyBatis认为的完全相同的查询,不是指使用sqlSession查询时传递给算起来Session的所有参数值完全相同,你只要保证statementId,rowBounds,最后生成的SQL语句,以及这个SQL语句所需要的参数完全一致就可以了。 

性能分析

1.MyBatis 对会话(Session)级别的一级缓存设计的比较简单,简单使用 HashMap 来维护,并没有对其容量大小进行限制

如果一直使用某一个 SqlSession 对象查询数据,是否会导致 HashMap 太大而导致 java.lang.OutOfMemoryError 错误?

  1. 一般而言 SqlSession 的生存时间很短,一般情况下使用一个 SqlSession 对象执行的操作不会太多,执行完就会消亡
  2. 对于某一个 Sqlsession 对象而言,只要执行 update 操作(update、insert、delete),都会将这个 SqlSession 对象中对应的一级缓存清空,所以一般不会出现缓存过大,影响 JVM 内存空间的问题
  3. 也可以手动地释放掉 SqlSession 对象中的缓存(clearCache()、flush…)

2. 一级缓存是一个粗粒度的缓存,没有更新缓存和缓存过期的概念

MyBatis 的一级缓存使用了简单的 HashMap,MyBatis 只负责将查询数据库的结果存到缓存中去,不会去判断缓存存放的时间是否过长,是否过期,因此也就没有对缓存结果进行更新的说法。

3.注意点

对于数据变化频率大,并且需要高时效准确性的数据要求,我们使用 SqlSession 查询的时候,要控制好 SqlSession 的生存时间,SqlSession 的生存时间越长,他缓存的数据可能越旧,从而造成和真实数据库的误差;同时对于这种情况用户可以手动适当的情况SqlSession中的缓存
对于只执行、并且频繁执行大范围的 select 操作的 SqlSession 对象,SqlSession 对象的生存时间不宜过长 

简要流程

详细流程 

二级缓存

简述

说明

一级缓存中,其最大的共享范围就是一个 SqlSession 内部,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。

MyBatis的二级缓存实现可以有很多种,可以是MemCache、Ehcache、Redis等,但是需要额外的Jar包。

开启二级缓存后,查询的逻辑是:二级缓存-=> 一级缓存=> 数据库;

二级缓存的缺点

  1.  一个Spring里边只有一个SQLSession,所以根本用不到二级缓存
  2. 二级缓存默认是不支持分布式的。虽然可以用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,因此此时一级缓存是有效的。

实例

原理

0

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录