mybatis一级缓存和二级缓存使用详解

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: mybatis一级缓存和二级缓存使用详解

一、概念说明


myabtis的缓存分为一级缓存和二级缓存,默认开启一级缓存,关闭二级缓存,

一级缓存时sqlSession级别,二级缓存是namespace级别。


1、一级缓存

mybatis的一级缓存默认是开启的,它在一个sqlSession会话里面的所有查询操作都会保存到缓存中,一般来说一个请求中的所有增删改查操作都是在同一个sqlSession里面的,所以我们可以认为每个请求都有自己的一级缓存,如果同一个sqlSession会话中2个查询中间有一个 insert 、update或delete 语句,那么之前查询的所有缓存都会清空。


使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话,在对数据库的一次会话中, 有可能会反复地执行完全相同的查询语句,每一次查询都会去查一次数据库,为了减少资源浪费,mybaits提供了一种缓存的方式(一级缓存)。


一级缓存失效:

如果同一个sqlSession会话中2 个查询中间有一个 insert 、update或delete 语句,那么之前查询的所有缓存都会清空。因为每次增删改操作都有可能会改变原来的数据,所以必须刷新缓存;


2、二级缓存

二级缓存是全局的,也就是说;多个请求可以共用一个缓存,二级缓存需要手动开启。


二级缓存针对的是同一个namespace,所以建议是在单表操作的Mapper中使用,或者是在相关表的Mapper文件中共享同一个缓存。


一级缓存无过期时间,只有生命周期,缓存会先放在一级缓存中,当sqlSession会话提交或者关闭时才会将一级缓存刷新到二级缓存中;开启二级缓存后,用户查询时,会先去二级缓存中找,找不到在去一级缓存中找,然后才去数据库查询;


二级缓存失效:

所有的update操作(insert,delete,uptede)都会触发缓存的刷新,从而导致二级缓存失效,所以二级缓存适合在读多写少的场景中开启。


二级缓存过期时间:

需要注意的是,并不是key-value的过期时间,而是这个cache的过期时间,是flushInterval,意味着整个清空缓存cache,所以不需要后台线程去定时检测。

每当存取数据的时候,都有检测一下cache的生命时间,默认是1小时,如果这个cache存活了一个小时,那么将整个清空一下。


3、比较

开启方式

一级缓存是默认开启的,二级缓存默认关闭。


作用范围

一级缓存是会话级别的缓存,即sqlSession级别,会话结束,清除会话中的缓存数据,实际代码中通过通过开启事务让多个数据库操作共享一个sqlSession。

二级缓存: 全局级别,也叫namespace级别,会话结束,缓存依然存在,多个请求可以共享缓存数据。


缓存位置

一级缓存由于是sqlSession级别,本质上是在JVM中创建一个Map集合对象保存缓存数据,所以缓存数据保留的地方是本地JVM内存中。

二级缓存默认也是保存在JVM中,但是可以通过配置将缓存数据保存到第三方缓存中,比如ehcache、redis。保存在redis这些的分布式缓存中,能提供更好的分布式场景的支持。


缓存过期

一级缓存无过期时间,只有生命周期,缓存会先放在一级缓存中,当sqlSession会话提交或者关闭时才会将一级缓存刷新到二级缓存中;开启二级缓存后,用户查询时,会先去二级缓存中找,找不到在去一级缓存中找,然后才去数据库查询;

二级缓存的过期时间默认是1小时,如果这个cache存活了一个小时,那么将整个清空一下。需要注意的是,并不是key-value的过期时间,而是这个cache的过期时间,是flushInterval,意味着整个清空缓存cache,所以不需要后台线程去定时检测,每当存取数据的时候,都有检测一下cache的生命时间。


小结:

一级缓存的作用在我看来在实际业务场景中作用真的非常有限,因为需要在一个事务方法中重复查询的需求场景真的太少,而且由于Mysql数据库的MVCC机制以及事务隔离机制-可重复读的能力,会导致同一个事务方法内多次执行相同的查询必定会得到相同的结果,所以在事务范围内的重复查询基本没什么实际作用。

设计一级缓存设计的意义,可能更多的是为二级缓存的实现做铺垫。

所以,如果关闭了mybatis的一级缓存,二级缓存将不会生效。



二、mybatis缓存的生命周期

mybatis的查询缓存会先放在一级缓存中,当sqlSession会话提交或者关闭时才会将一级缓存刷新到二级缓存中;

开启二级缓存后,用户查询时,会先去二级缓存中找,找不到在去一级缓存中找,然后才去数据库查询;

119.png

118.png




三、一级缓存的使用


spring开启事务管理的方法在方法执行过程中只会创建一个sqlsession。

这是因为事务管理下的sql执行方式是BATCH,只会与数据库交互一次,一次执行完所有的sql,所以只会创建一个sqlsession;


不开启事务:

@Override
public GoodsStock getStock(Integer goodsId) {
    log.info("第1次查询");
    GoodsStock goodsStock = goodsStockMapper.getStock(goodsId);
    log.info("第2次查询");
    goodsStock = goodsStockMapper.getStock(goodsId);
    return goodsStock;
}


执行结果:

117.png


开启事务:

@Override
@Transactional(rollbackFor = Exception.class)
public GoodsStock getStock(Integer goodsId) {
    log.info("第1次查询");
    GoodsStock goodsStock = goodsStockMapper.getStock(goodsId);
    log.info("第2次查询");
    goodsStock = goodsStockMapper.getStock(goodsId);
    return goodsStock;
}


执行结果:

116.png

可以看到,第2次查询并没有打印查询SQL,说明命中了缓存。


关闭一级缓存:

在配置文件中添加如下属性

#一级缓存关闭,默认值SESSION,表示开启一级缓存,statement表示关闭一级缓存

mybatis.configuration.local-cache-scope=statement


再次请求:

115.png

通过添加配置关闭mybatis的一级缓存后,第2次查询打印了SQL,进行了查库操作。


说实话,一级缓存比较鸡肋,一般在同一个方法内,很少会进行重复的查询,在实际业务中的真实使用场景暂时没有想到。目前的实际意义更多的是为mybatis的二级缓存做一个铺垫。


关于一级缓存的使用注意:

1、一般在一个方法内需要用相同条件查询多次的场景其实非常少见,因为方法体内对象都是可见共享的,没必要再次进行查询。

2、如果需要再次查询判断之前的查询结果是否已经出现变更,这时除了需要关闭一级缓存外,还需要注意事务的隔离级别。

比如,

如果事务隔离级别为可重复读时,在本事务中尽管执行多次查询,由于会对查询的数据在事务范围内加读锁,所以查询的数据一定是相同的,没有重复查下的必要。

如果事务隔离级别为读已提交,需要进行重复查询判断数据的变化情况,那么需要关闭mybatis的一级缓存。


不过,推荐在需要多次查询做数据比对的场景,最好还是关闭事务,这样就可以保障每次查询都进行查库操作,获取最新的数据。


事务为可重复读级别REPEATABLE_READ,没有再次查询的必要,数据一定不会发生变更:

@Override
@Transactional(rollbackFor = Exception.class,isolation=Isolation.REPEATABLE_READ,readOnly = true)
public GoodsStock getStock(Integer goodsId) {
    log.info("第1次查询");
    //由于是REPEATABLE_READ可重复读隔离级别,在事务范围内会对读取到的数据加锁,所以读取到的数据一定不会发生变更,没有再次查询的必要
    GoodsStock goodsStock = goodsStockMapper.getStock(goodsId);
    log.info("第2次查询");
    goodsStock = goodsStockMapper.getStock(goodsId);
    return goodsStock;
}


事务为读已提交READ_COMMITTED,由于读锁会立即释放,导致事务范围内已经查询到的数据可能被其他事务修改,如果需要重复查库判断数据变化,那么必须关闭mybatis的一级缓存,不然2次查询不会进行查库操作。

如果在1次查询和2 次查询中的逻辑处理有一个 insert 、update或delete 操作,那么之前查询的所有缓存都会清空。

2次查询也会进行查库操作。


@Override
@Transactional(rollbackFor = Exception.class,isolation=Isolation.READ_COMMITTED,readOnly = true)
public GoodsStock getStock(Integer goodsId) {
    log.info("第1次查询");
    //由于是REPEATABLE_READ可重复读隔离级别,在事务范围内会对读取到的数据加锁,所以读取到的数据一定不会发生变更,没有再次查询的必要
    GoodsStock goodsStock1 = goodsStockMapper.getStock(goodsId);
    log.info("逻辑处理");
    log.info("第2次查询,判断变化,需要关闭mybatis的一级缓存");
    GoodsStock goodsStock2 = goodsStockMapper.getStock(goodsId);
    return goodsStock2;
}


四、二级缓存的使用


二级缓存相关的配置有三个地方:

1、mybatis-config中有一个全局配置属性,这个不配置也行,因为默认就是true。

<setting name="cacheEnabled" value="true"/>


spring boot中也可以通过配置项开启二级缓存:


#二级缓存开启
mybatis.configuration.cache-enabled=true


2、在Mapper映射文件内需要配置缓存标签:


<cache eviction="LRU" flushInterval="60000" size="1000"/>


配置项说明:


eviction 淘汰策略,LRU、FIFO

size 缓存个数

flushInterval 刷新频率,即缓存过期时间

LruCache LRU淘汰策略缓存(默认淘汰策略) 当缓存达到上限,删除最近最少使用缓存 eviction=“LRU”

FifoCache FIFO淘汰策略缓存 当缓存达到上限,删除最先入队的缓存 eviction=“FIFO”


3、在select查询语句标签上配置useCache属性,如下:

<select id="selectUserAndJob" resultMap="JobResultMap2" useCache="true">
      select * from lw_user
  </select>

以上配置第1点是默认开启的,也就是说我们只要配置第2点就可以打开二级缓存了,而第3点是当我们需要针对某一条语句来配置二级缓存的时候则可以使用。


4、开启二级缓存后,映射的pojo对象需要实现序列化接口

114.png


执行结果报序列化异常,那是因为我们映射的pojo对象未实现序列化接口,说明我们从缓存数据中读取数据需要进行反序列化,这是因 为mybatis的二级缓存的缓存介质有多种多样,而并不一定是在内存中,所以需要我们对pojo对象进行序列化,只要实现序列化接口即可。


五、自定义二级缓存


默认的二级缓存也是存储在本地缓存,对于微服务下是可能出现脏读的情况的,这时可能会需要自定义缓存,比如利用redis来存储缓存,而不是存储在本地内存当中。


MyBatis官方也提供了第三方缓存的支持引入pom文件:

<dependency>
  <groupId>org.mybatis.caches</groupId>
  <artifactId>mybatis-redis</artifactId>
  <version>1.0.0-beta2</version>
</dependency>

然后缓存配置如下:


<cache type="org.mybatis.caches.redis.RedisCache"></cache>


然后在默认的resource路径下新建一个redis.properties文件:


host=localhost
port=6379


六、mybatis缓存、spring缓存和redis缓存的使用比较


mybatis缓存——dao层的缓存,主要是对select查库操作的结果进行缓存。

spring缓存——方法级别的缓存,主要通过在方法上添加缓存注解@CachePut、@CacheEvict来实现。

redisTemplate缓存——代码级别的缓存,主要是在方法体内部通过redisTemplate模板类编码控制缓存读写。

mybatis缓存只能对sql查询结果进行缓存,spring 缓存只能对方法执行结果进行缓存,不能对方法执行过程中的中间对象进行缓存。这两者的优点都是使用简单,只需简单的配置或添加注解即可实现缓存,基本不需要编码。

缺点是缓存的控制范围都是方法级别,不能缓存方法执行过程中的中间对象,有一定的使用局限性。


而通过redisTemplate进行编码控制缓存,优点是控制灵活,缺点是需要编码实现。


实际项目中大家可以根据需要选择怎么使用缓存,但是要注意,针对单一方法,缓存不要混用,容易造成逻辑混乱,定位问题困难等问题。


总结


本文主要是针对mybatis的一级缓存和二级缓存的区别和使用进行了详细介绍。重点掌握一下方面

1、一级缓存和二级缓存的区别

2、mybatis缓存的生命周期

3、如何开启二级缓存,二级缓存什么时候失效,如何将本地的二级缓存升级到采用redis支持分布式的二级缓存?

4、二级缓存的淘汰策略有哪些

5、mybatis缓存、spring缓存和redis缓存的比较。


mybatis的一级缓存是为了二级缓存做铺垫,如果采用二级缓存,推荐升级到采用redis存储缓存数据,这样能更好的支持分布式。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
存储 缓存 芯片
让星星⭐月亮告诉你,当我们在说CPU一级缓存二级缓存三级缓存的时候,我们到底在说什么?
本文介绍了CPU缓存的基本概念和作用,以及不同级别的缓存(L1、L2、L3)的特点和工作原理。CPU缓存是CPU内部的存储器,用于存储RAM中的数据和指令副本,以提高数据访问速度,减少CPU与RAM之间的速度差异。L1缓存位于处理器内部,速度最快;L2缓存容量更大,但速度稍慢;L3缓存容量最大,由所有CPU内核共享。文章还对比了DRAM和SRAM两种内存类型,解释了它们在计算机系统中的应用。
80 1
|
3月前
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
24天前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
25天前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
63 5
|
6月前
|
SQL 缓存 Java
MYBATIS缓存
MYBATIS缓存
|
2月前
|
缓存 Java 数据库连接
使用MyBatis缓存的简单案例
MyBatis 是一种流行的持久层框架,支持自定义 SQL 执行、映射及复杂查询。本文介绍了如何在 Spring Boot 项目中集成 MyBatis 并实现一级和二级缓存,以提高查询性能,减少数据库访问。通过具体的电商系统案例,详细讲解了项目搭建、缓存配置、实体类创建、Mapper 编写、Service 层实现及缓存测试等步骤。
|
5月前
|
SQL 缓存 Java
【面试官】Mybatis缓存有什么问题吗?
面试官:你说下对MyBatis的理解?面试官:那SqlSession知道吧?面试官:Mybatis的缓存有哪几种?面试官:那Mybatis缓存有什么问题吗?面试官:Mybatis分页插件是怎么
【面试官】Mybatis缓存有什么问题吗?
|
5月前
|
缓存 算法 Java
关于MyBatis的缓存详解
MyBatis 的缓存机制非常灵活,可以通过简单的配置来满足不同的性能需求。合理地使用缓存可以显著提高应用程序的性能,尤其是在处理大量数据库查询时。然而,开发者需要注意缓存的一致性和并发问题,特别是在使用可读写缓存时。
|
4月前
|
存储 缓存 Java
|
6月前
|
缓存 NoSQL Java
在 SSM 架构(Spring + SpringMVC + MyBatis)中,可以通过 Spring 的注解式缓存来实现 Redis 缓存功能
【6月更文挑战第18天】在SSM(Spring+SpringMVC+MyBatis)中集成Redis缓存,涉及以下步骤:添加Spring Boot的`spring-boot-starter-data-redis`依赖;配置Redis连接池(如JedisPoolConfig)和连接工厂;在Service层使用`@Cacheable`注解标记缓存方法,指定缓存名和键生成策略;最后,在主配置类启用缓存注解。通过这些步骤,可以利用Spring的注解实现Redis缓存。
80 2