SpringCache 缓存 - @Cacheable、@CacheEvict、@CachePut、@Caching、CacheConfig 以及优劣分析

news2024/11/20 21:30:44

 

目录

SpringCache 缓存

环境配置

1)依赖如下

2)配置文件

3)设置缓存的 value 序列化为 JSON 格式

4)@EnableCaching 

实战开发

@Cacheable

@CacheEvict

@CachePut

@Caching

@CacheConfig

SpringCache 的优势和劣势

读操作(优势)

写操作(劣势)

总结


SpringCache 缓存


环境配置

1)依赖如下

父依赖 SpringBoot 3.2.5

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>

2)配置文件

spring:
  cache:
    type: redis
    redis:
      time-to-live: 3600000
      # key-prefix: CACHE_
      use-key-prefix: true 
      cache-null-values: true
  • time-to-live: 3600000 -> 缓存过期时间,单位毫秒,此处相当于 1 小时(实际上也就解决了雪崩问题,因为一般设置每一个缓存时的时间线不一样)
  • key-prefix: CACHE_ -> 缓存 key 前缀(一般不用这个属性,而是使用分区名作为 key 前缀)
  • use-key-prefix: true -> 是否使用缓存分区名作为 key 前缀(分区名在 @Cacheable 中指定),建议为 true
  • cache-null-values: true -> 是否缓存空值(解决缓存穿透问题),建议为 true

3)设置缓存的 value 序列化为 JSON 格式

import org.springframework.boot.autoconfigure.cache.CacheProperties
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.cache.annotation.EnableCaching
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.cache.RedisCacheConfiguration
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
import org.springframework.data.redis.serializer.RedisSerializationContext
import org.springframework.data.redis.serializer.StringRedisSerializer

@Configuration
@EnableConfigurationProperties(CacheProperties::class) //让配置文件中的配置生效
@EnableCaching // 开启 SpringCache 缓存功能(如果这里不写这个注解,启动类上也一定要有!!!)
class MyCacheConfig {

    @Bean
    fun redisCacheConfiguration(
        cacheProperties: CacheProperties
    ): RedisCacheConfiguration {
        //这里源码怎么写,咱们咱们写(只需要改一下缓存 value 的序列化方式即可)
        var config = RedisCacheConfiguration.defaultCacheConfig()

        //设置 key value 的序列化方式
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer()))
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(GenericJackson2JsonRedisSerializer()))

        val redisProperties = cacheProperties.redis

        //将配置文件中的所有配置都生效
        redisProperties.timeToLive?.let {
            config = config.entryTtl(it)
        }
        redisProperties.keyPrefix?.let {
            config = config.prefixCacheNameWith(it)
        }
        if (!redisProperties.isCacheNullValues) {
            config = config.disableCachingNullValues()
        }
        if (!redisProperties.isUseKeyPrefix) {
            config = config.disableKeyPrefix()
        }
        return config
    }

}

4)@EnableCaching 

@EnableCaching 表示开启 SpringCache 缓存功能,加在 启动类 或者 配置类 上都可以.

实战开发

@Cacheable

a)使用说明:

@Cacheable 用来将方法的返回值数据保存到缓存中.

常用属性如下:

  • value:表示将当前缓存数据放到哪个 缓存组 中(可以理解为放到哪个文件夹下). 
    • 例如 @Cacheable(value = ["user"])
  • key:指定 key 是什么.  接受一个 SpEL 表达式,例如如下表格中的示例
    • 例如方法名作为 key:@Cacheable(value = ["user"], key = "#root.method.name")
    • 另外,如果不想使用 SpEL 表达式,可以直接在双引号内加上一对单引号,例如 key 为 "userinfo":@Cacheable(value = ["user"], key = "'userinfo'")
  • condition:条件判断属性,只有符合条件才可以被缓存.
    • 例如方法参数中的 id > 0 返回值才能被缓存 @Cacheable(value = ["user"], key = "#root.method.name", condition = "#id > 0")
  • sync:是否为同步执行. 如果设置为 true,会加锁(本地锁),可以用来解决击穿问题.

b)案例如下:

例如通过 SpEL表达式设置 缓存的 key 为 动态的id + "userinfo" ,

    @Cacheable(value = ["user"], key = "#id + 'userinfo'")
    override fun getUserinfo(id: Long): UserinfoVo {
        //业务逻辑...
        println("查询数据库...")
        return UserinfoVo( // 这里的 UserinfoVo 必须要有无参构造才行,否则缓存将来读取的时候会报错
            id = id,
            name = "cyk",
            age = 21,
        )
    }

第一查询之后,就可以看到 Redis 上面已经存在该数据.  之后的只要缓存未过期,都会直接查缓存.

@CacheEvict

a)使用说明

@CacheEvict 用来将数据从缓存中删除. 

他常常被用来实现 “失效模式” 来解决缓存一致性问题(数据库中的数据被更新之后,直接删除缓存上的数据即可,下次查询的时候,自动同步到缓存上).

常用属性和 @Cacheable 差不多,这里不再赘述.

b)案例如下

例如实现缓存失效:现在要进行用户信息的修改,那么为了保证缓存和数据库中数据一致,修改完数据库之后的就直接删除对应的缓存数据即可~  下次查询时,再更新缓存.

这里通过 SpEL 表达式设置要删除的缓存的 key 为 动态的id + "userinfo" ,

    /**
     *  通过 @CacheEvict 实现缓存失效,下次查询时,再更新缓存
     */
    @CacheEvict(value = ["user"], key = "#dto.id + 'userinfo'")
    override fun updateUserinfo(dto: UserinfoDto) {
        //业务逻辑...
        println("修改数据库数据...")
    }

另外,还可以通过 属性,删除同一个分区下的所有缓存(慎用)

@CacheEvict(value = ["user"],  allEntries = true)

@CachePut

a)使用说明

@CachePut 用来更新缓存数据. 

与 @Cacheable 不同的是,使用 @CachePut 标注的方法在执行前不会检查缓存中是否存在这个数据,而是每次都会执行这个方法,并将返回值写入到缓存中.

属性上和 @Cacheable 是一样的,这里不再赘述.

b)案例如下

    @CachePut(value = ["user"], key = "#dto.id + 'userinfo'")
    override fun putUserinfo(dto: UserinfoDto): UserinfoVo {
        //业务逻辑
        println("更新数据库...")
        return with(dto) {
            UserinfoVo(
                id = id,
                name = name,
                age = age,
            )
        }
    }

@Caching

@Caching 用来组合以上多个操作.

例如删除同时删除多个缓存数据

    @Caching(evict = [
        CacheEvict(value = ["user"], key = "#dto.id + 'userinfo'"),
        CacheEvict(value = ["user"], key = "#dto.id + 1 + 'userinfo'"),
    ])
    override fun updateUserinfo(dto: UserinfoDto) {
        //业务逻辑...
        println("修改数据库数据...")
    }

@CacheConfig

如果一个类中有很多一样的 cacheName、keyGenerator、cacheManager、cacheResolver,可以直接使用 @CacheConfig 在类上声明,那么这个类中的所有标记了 Cache 相关注解的方法都会共享 @CacheConfig 属性

@Service
//@CacheConfig(cacheNames = ["aaa", "bbb"]) 会创建两个缓存分区, aaa 和 bbb
@CacheConfig(cacheNames = ["user"])
class CacheServiceImpl: CacheService {

    @Cacheable(key = "#id + 'userinfo'")
    override fun getUserinfo(id: Long): UserinfoVo {
        //业务逻辑...
        println("查询数据库...")
        return UserinfoVo( // 这里的 UserinfoVo 必须要有无参构造才行,否则缓存将来读取的时候会报错
            id = id,
            name = "cyk",
            age = 21,
        )
    }

    /**
     *  通过 @CacheEvict 实现缓存失效,下次查询时,再更新缓存
     */
    @CacheEvict(key = "#dto.id + 'userinfo'")
    override fun updateUserinfo(dto: UserinfoDto) {
        //业务逻辑...
        println("修改数据库数据...")
    }

    @CachePut(key = "#dto.id + 'userinfo'")
    override fun putUserinfo(dto: UserinfoDto): UserinfoVo {
        //业务逻辑
        println("更新数据库...")
        return with(dto) {
            UserinfoVo(
                id = id,
                name = name,
                age = age,
            )
        }
    }

}

SpringCache 的优势和劣势

读操作(优势)

SpringCache 在读操作上的处理的还是很到位的:

  • 缓存穿透:配置文件中设置 cache-null-values: true,这样就会将查询为 null 也缓存起来.
  • 缓存击穿:配置文件中设置 sync=true,这样就可以对方法进行加锁,解决击穿问题.
  • 缓存雪崩:配置文件中设置 time-to-live=3600000 用来设置过期时间(虽然设置的时间是统一的,但是一般情况下情况下触发的时机是不同的,也就相当于是有了随机因子).

写操作(劣势)

  • 对于读写并发高,或者写并发高的场景不太好应对.
  • 针对于一些特殊的写场景,还是要定制化一下的

总结

对于读多写少,一致性要求不高的数据,完全可以使用 SpringCache 来简化开发(只要缓存的数据有过期时间就可以).

对于一致性要求高的场景,也没必要引入引入缓存,直接对数据库进行读写即可.

特殊数据特殊处理.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1791334.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

出行预测:端午打车需求将上涨31%,滴滴发放超2亿司机补贴

作为上半年的“收官”小长假&#xff0c;端午假期接棒“五一”的出行热度&#xff0c;中短途周边游持续升温&#xff0c;海滨旅行、龙舟民俗体验成为新的出行看点。 滴滴出行预测&#xff0c;端午节当天&#xff08;6月10日&#xff09;打车需求将同比去年上涨约31%。今年端午…

因子区间[牛客周赛44]

思路分析: 我们可以发现125是因子个数的极限了,所以我们可以用二维数组来维护第几个数有几个因子,然后用前缀和算出来每个区间合法个数,通过一个排列和从num里面选2个 ,c num 2 来计算即可 #include<iostream> #include<cstring> #include<string> #include…

长虹智能电视55D3P(机芯:ZLH74GiR2G)海思平台固件解析打包

一、使用Hitool打包固件 接上一篇&#xff0c;尝试使用HITOOL打包固件 长虹55D3P海思平台固件破解-CSDN博客 参考ZNDS HItool备份固件&#xff1a;【玩机必看】海思机顶盒备份线刷包 制作分区表xml文件_ZNDS刷机/救砖_ZNDS HITOOL下载&#xff1a;https://cloud.189.cn/web/…

关于信号翻转模块(sig_flag_mod)的实现

关于信号翻转模块(sig_flag_mod)的实现 语言 &#xff1a;Verilg HDL 、VHDL EDA工具&#xff1a;ISE、Vivado、Quartus II 关于信号翻转模块(sig_flag_mod)的实现一、引言二、实现信号翻转模块的方法&#xff08;1&#xff09;输入接口&#xff08;2&#xff09;输出接口&…

如何跨渠道分析销售数据 - 7年制造业销售经验小结

如何跨渠道分析销售数据 - 7年制造业销售经验小结&#xff08;1&#xff09; 【前言】 在我过去7年销售工作生涯中&#xff0c;从第一年成为公司销冠后&#xff0c;我当时的确自满的一段时间&#xff0c;认为自己很了不起。但是第一年的销售业绩并没有拿到提成&#xff0c;最…

LabVIEW冲击响应谱分析系统

LabVIEW冲击响应谱分析系统 开发了一种基于LabVIEW开发的冲击响应谱分析系统&#xff0c;该系统主要用于分析在短时间内高量级输入力作用下装备的响应。通过改进的递归数字滤波法和样条函数法进行冲击响应谱的计算&#xff0c;实现了冲击有效持续时间的自动提取和响应谱的精准…

分布式任务队列系统 celery 进阶

通过前面的入门&#xff0c;我们大概了解了celery的工作原理及简单的入门代码示例&#xff08;传送门&#xff09;&#xff0c;下面进行一些稍微复杂的任务调度学习 多目录结构异步执行 在实际项目中&#xff0c;使用Celery进行异步任务处理时&#xff0c;经常需要将代码组织…

CTF本地靶场搭建——基于阿里云ACR实现动态flag题型的创建

接上文&#xff0c;这篇主要是结合阿里云ACR来实现动态flag题型的创建。 这里顺便也介绍一下阿里云的ACR服务。 阿里云容器镜像服务&#xff08;简称 ACR&#xff09;是面向容器镜像、Helm Chart 等符合 OCI 标准的云原生制品安全托管及高效分发平台。 ACR 支持全球同步加速、…

STM32作业实现(七)OLED显示数据

目录 STM32作业设计 STM32作业实现(一)串口通信 STM32作业实现(二)串口控制led STM32作业实现(三)串口控制有源蜂鸣器 STM32作业实现(四)光敏传感器 STM32作业实现(五)温湿度传感器dht11 STM32作业实现(六)闪存保存数据 STM32作业实现(七)OLED显示数据 STM32作业实现(八)触摸按…

6 大亮点!全新 Anolis OS 23.1 GA 版正式发布,满足多样化平台支持

一、引言 Anolis OS 23&#xff0c;作为龙蜥社区推出的着重于技术演进和先进性的 Linux 发行版本&#xff0c;即便在频繁集成各类软件最新特性的同时&#xff0c;依然确保了系统的高度稳定性和可靠性。Anolis OS 在社区共建上对理事单位的需求给予了极大重视&#xff0c;力保各…

使用正则表达式分割字符串

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 split()方法用于实现根据正则表达式分割字符串&#xff0c;并以列表的形式返回。其作用同字符串对象的split()方法类似&#xff0c;所不同的就是分割…

Linux下的配置工具menuconfig+配置文件(Kconfig/.config/defconfig)

我们都知道,嵌入式开发中,或者说C语言中,配置基本都是通过宏定义来决定的,在MCU开发中,代码量比较小,配置项也比较少,我们直接修改对应的宏定义即可。 但是,Linux开发中,操作系统、驱动部分还有应用部分加起来,代码量极大,配置项目也非常多,这时候,就需要对这些配…

HTML+CSS+JS 动态登录表单

效果演示 实现了一个登录表单的背景动画效果,包括一个渐变背景、一个输入框和一个登录按钮。背景动画由多个不同大小和颜色的正方形组成,它们在页面上以不同的速度和方向移动。当用户成功登录后,标题会向上移动,表单会消失。 Code <!DOCTYPE html> <html lang=&q…

小白学大模型:Hugging Face Tokenizer

Tokenizer介绍 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;Tokenizer&#xff08;分词器&#xff09;是准备输入模型的关键步骤之一。Hugging Face 提供了用于各种模型的分词器库&#xff0c;其中大多数分词器都以两种风格提供&#xff1a;一种是完整的 Pytho…

VRRP

文章目录 VRRP基本原理技术背景VRRP作用VRRP概述VRRP报文VRRP名词解释VRRP路由器VRRP组虚拟路由器虚拟IP地址、MAC地址Master、Backup路由器 VRRP状态机Master/ Backup 路由器Master路由器:Backup路由器: VRRP的工作过程 VRRP基础配置 VRRP基本原理 技术背景 为了解决单个路由…

力扣hot100:138. 随机链表的复制(技巧,数据结构)

LeetCode&#xff1a;138. 随机链表的复制 这是一个经典的数据结构题&#xff0c;当做数据结构来学习。 1、哈希映射 需要注意的是&#xff0c;指针也能够当做unordered_map的键值&#xff0c;指针实际上是一个地址值&#xff0c;在unordered_map中&#xff0c;使用指针的实…

快速C++中的入门智能指针

✨前言✨ &#x1f4d8; 博客主页&#xff1a;to Keep博客主页 &#x1f646;欢迎关注&#xff0c;&#x1f44d;点赞&#xff0c;&#x1f4dd;留言评论 ⏳首发时间&#xff1a;2024年6月4日 &#x1f4e8; 博主码云地址&#xff1a;博主码云地址 &#x1f4d5;参考书籍&#…

【数据集划分】假如你有超百万条oracle数据库数据(成真版)

【数据集划分】假如你有接近百万条oracle数据库数据&#xff08;成真版&#xff09; 写在最前面小结 数据集划分原因注意事项 1. 留出法&#xff08;Hold-out Method&#xff09;原理算法复杂度代码示例Scikit-learn的train_test_split分布式计算框架&#xff08;如Apache Spar…

JVM学习-Jprofiler

JProfiler 基本概述 特点 使用方便&#xff0c;界面操作友好对被分析的应用影响小(提供模板)CPU&#xff0c;Tread&#xff0c;Memory分析功能尤其强大支持对jdbc,noSql,jsp,servlet,socket进行分析支持多种模式(离线、在线)的分析支持监控本地、远程JVM跨平台&#xff0c;拥…

MongoDB~索引使用与优化

Study by&#xff1a; https://docs.mongoing.com/indexeshttps://www.cnblogs.com/Neeo/articles/14325130.html#%E5%85%B6%E4%BB%96%E7%B4%A2%E5%BC%95 作用 如果你把数据库类比为一本书&#xff0c;那书的具体内容是数据&#xff0c;书的目录就是索引&#xff0c;所以索引…