【MyBatis】SpringBoot 和 MyBatis 一级缓存和二级缓存的介绍与代码实践

33 篇文章 3 订阅
31 篇文章 4 订阅

一、概念介绍

1、什么是一级缓存

在日常开发过程中,经常会有相同的 sql 执行多次查询的情况,Mybatis 提供了一些缓存来优化这些查询,避免多次请求数据库。一级缓存在 mybatis 中默认是开启的并且是 session 级别(但是想其生效,需要使用事务),它的作用域为一次 sqlSession 会话,在代码中需要使用事务才能生效。

        一级缓存是 SqlSession 级别的缓存。在操作数据库时需要构造 sqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,缓存是 key-value 形式,key 可以看作 select 语句(key 还包含其他信息)。不同的 sqlSession 之间的缓存数据区域(HashMap)是互相隔离的。

2、什么是二级缓存

        相对于一级缓存,二级缓存的作用域更广泛,它是 Mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 可以共用二级缓存。mybatis 的二级缓存默认也是开启的(但是想其生效,需要在 xml 文件中添加 cache 标签),但由于它的作用域是 namespace ,所以在代码中还需要在 mapper.xml 中写上 cache 标签才能生效。

3、缓存的优先级

        通过 mybatis 发起的查询,作用顺序为:二级缓存 -> 一级缓存 -> 数据库。

4、缓存失效

        当在一个缓存作用域中发生了 update、insert、delete 动作后,将会触发缓存失效,下一次查询将命中数据库,从而保证不会查到脏数据。

二、一级缓存(local cache、本地缓存、作用域默认为 SqlSession)

注意:对于会话(Session)级别的数据缓存,我们称之为一级数据缓存,简称一级缓存。

        每当使用 Mybatis 开启一次和数据库的会话时,Mybatis 会创建出一个 SqlSession 对象表示一次数据库会话。在对数据库的一次会话中,有可能会反复的执行完全相同的查询语句,如果不采取一些措施,每一次查询都会查询一次数据库,而在极短的时间内做了完全相同的查询,它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。为了解决这一问题,减少资源的浪费,Mybatis 会在会话的 SqlSession 对象中一个简单的缓存,将每次查询到的结果缓存起来,下次查询时,判断先前若有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询。

1、代码演示

默认情况下,Mybatis 开启并使用了一级缓存(但是想其生效,需要使用事务)

单元测试用例:

@Test
@Transactional(rollbackFor = Throwable.class)
@Commit
public void a1() {
    // 第一次查询,缓存到一级缓存
    productModuleConfigDao.queryById("d4b8ff38-f973-4d11-8008-2ad37490651f");
    System.out.println("-------------------------------------------");
    // 第二次查询,直接读取一级缓存
    productModuleConfigDao.queryById("d4b8ff38-f973-4d11-8008-2ad37490651f");
}

执行结果:

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
JDBC Connection [HikariProxyConnection@2059701776 wrapping com.mysql.cj.jdbc.ConnectionImpl@52354202] will be managed by Spring
==>  Preparing: select ID, TYPE, NAME, PARENT_ID, CODE, ENABLE from product_module_config where ID = ?
==> Parameters: d4b8ff38-f973-4d11-8008-2ad37490651f(String)
<==    Columns: ID, TYPE, NAME, PARENT_ID, CODE, ENABLE
<==        Row: d4b8ff38-f973-4d11-8008-2ad37490651f, product, hr, null, d4b8ff38-f973-4d11-8008-2ad37490651f, 1
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
-------------------------------------------
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a] from current transaction
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]

控制台打印的信息进行翻译:

创建一个新的SqlSession
注册SqlSession的事务同步[org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
JDBC连接[HikariProxyConnection@2059701776包装com.mysql.cj.jdbc。]ConnectionImpl@52354202]将由Spring管理
发布事务性SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
-------------------------------------------
从当前事务获取SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
发布事务性SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
提交事务同步SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
事务同步注销SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]
事务同步关闭SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2801827a]

可以看到,虽然进行了两次查询,但最终只请求了一次数据库,第二次查询命中了一级缓存,直接返回了数据。

  • 为什么开启事务

        由于使用了数据库连接池,默认每次查询完之后自动 commite,这就导致两次查询使用的不是同一个 sqlSession。当我们开启了事务,两次查询都在同一个 sqlSession 中,从而让第二次查询命中了一级缓存。读者可以自行关闭事务验证此结论(关掉时候,会查询两次数据库)。

  • 两种一级缓存模式

        一级缓存的作用域有两种:session(默认)和 statment,可通过在配置文件中 mybatis.configuration.local-cache-scope=statement 的值来切换,默认为 session

        二者的区别在于 session 第二次查询相同的 sql 语句是查的缓存,不会去数据库去查询。而 statment 仅针对于第一次查询,第二次查询相同的 sql 还是从数据库中进行查询,所以 statment 可以理解为关闭一级缓存。

 

三、二级缓存(second level cache,全局作用域缓存) 

         二级缓存是基于 mapper 文件的 namespace 的,也就是说多个 sqlSession 可以共享一个 mapper 中的二级缓存区域,并且如果两个 mapper 的 namespace 相同,即使是两个 mapper,那么这两个 mapper 中执行 sql 查询到的数据也将存在相同的二级缓存区域中。

        由于 Mybatis 使用 SqlSession 对象表示一次数据库的会话,那么,对于会话级别的一级缓存也应该是在 SqlSession 中控制的。实际上, MyBtais 只是一个 MyBatis 对外的接口,SqlSession 将它的工作交给了 Executor 执行器这个角色来完成,而缓存信息被维护在这个 Executor 执行器中,Mybatis 将缓存和对缓存相关的操作封装成了 Cache 接口中,负责完成对数据库的各种操作。

 1、代码演示

        默认情况下,Mybatis 打开了二级缓存,但它并未生效,因为二级缓存的作用域是 namespace,所以还需要在 Mapper.xml 文件中配置一下 cache 标签才能使二级缓存生效。

  • 单表二级缓存
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>

属性释义:

eviction:缓存的回收策略,默认的是 LRU。

  • LRU - 最近最少使用,移除最长时间不被使用的对象。

  • FIFO - 先进先出,按对象进入缓存的顺序来移除它们。

  • SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象。

  • WEAK - 弱引用,更积极地移除基于垃圾收集器和弱引用规则的对象。

flushInterval:缓存刷新间隔。缓存多长时间清空一次,默认不清空,设置一个毫秒值。上面设置了100s,每隔100s就往数据库查询一次,再将结果放入到二级缓存中。

readOnly:是否只读。

  • true(只读):MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户 。不安全,速度快。

  • false(读写,默认):MyBatis 觉得获取的数据可能会被修改,MyBatis 会利用序列化或反序列化的技术克隆一份新的数据。安全,速度相对慢。

size:缓存存放多少个元素。

单元测试用例:

@Test
@Transactional(rollbackFor = Throwable.class)
@Commit
public void a1() {
    // 第一次查询,缓存到一级缓存
    productModuleConfigDao.queryById("d4b8ff38-f973-4d11-8008-2ad37490651f");
    System.out.println("-------------------------------------------");
    // 第二次查询,直接读取一级缓存
    productModuleConfigDao.queryById("d4b8ff38-f973-4d11-8008-2ad37490651f");
}

执行结果:

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
Cache Hit Ratio [com.example.gauditdemo.dao.ProductModuleConfigDao]: 0.0
JDBC Connection [HikariProxyConnection@725873428 wrapping com.mysql.cj.jdbc.ConnectionImpl@3a4a5f3c] will be managed by Spring
==>  Preparing: select ID, TYPE, NAME, PARENT_ID, CODE, ENABLE from product_module_config where ID = ?
==> Parameters: d4b8ff38-f973-4d11-8008-2ad37490651f(String)
<==    Columns: ID, TYPE, NAME, PARENT_ID, CODE, ENABLE
<==        Row: d4b8ff38-f973-4d11-8008-2ad37490651f, product, hr, null, d4b8ff38-f973-4d11-8008-2ad37490651f, 1
<==      Total: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
-------------------------------------------
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202] from current transaction
Cache Hit Ratio [com.example.gauditdemo.dao.ProductModuleConfigDao]: 0.5
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]

控制台的打印信息进行打印:

创建一个新的SqlSession
注册SqlSession的事务同步[org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
缓存命中率[com.example.gauditdemo.dao]。ProductModuleConfigDao): 0.0
发布事务性SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
-------------------------------------------
从当前事务获取SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
缓存命中率[com.example.gauditdemo.dao]。ProductModuleConfigDao): 0.5
发布事务性SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
提交事务同步SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
事务同步注销SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]
事务同步关闭SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@52354202]

这里可以看到,第一次没有命中缓存,则查询了数据库,第二次再次查询,直接命中了缓存,日志还打印了该缓存的命中率。

四、总结

  • Mybatis 默认的 session (会话)级别一级缓存,需要开启事务才有用。但一级缓存作用域仅限同一 sqlSession 内,无法感知到其他 sqlSession 的增删改,所以极易产生脏数据。所以生产环境建议将一级缓存设置为 statment 级别(即关闭一级缓存),如果有必要,可以开启二级缓存,但是前提是单机环境,并且符合业务需求
  • 对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用 mybatis 二级缓存降低数据库访问量,提高访问速度。
  • 如果应用是分布式部署,由于二级缓存存储在本地,每个服务缓存相互隔离,如果其中一个服务修改了数据库数据,其它的几个服务还是查询的自己的本地缓存,必然导致查询出脏数据(除非自己设置 flushInterval 刷新时间),所以,分布式部署的应用不建议开启
  • mybatis 二级缓存针对大多数的业务系统都不推荐使用,因为业务系统数据操作比较频繁、自带的二级缓存性能也不是很高。二级缓存很难起到实际的作用。可以引用第三方缓存

五、参考文档

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值