当前位置: 首页 > news >正文

Spring系列之Spring Cache缓存注解的使用

目录

一、概述

二、缓存注解

1、@Cacheable 缓存结果

2、@CachePut 更新缓存

3、@CacheEvict 清除缓存

4、@Caching 组合缓存(不常用)

5、@CacheConfig 类级别缓存配置(不常用)

6、@CacheResult 设置缓存超时(不常用)

三、使用方式

1、@EnableCaching开启缓存

2、在方法上添加注解@Cacheable等注解

四 、优劣势

五、总结


一、概述

Spring Cache 是 Spring Framework 提供的一个用于操作缓存的抽象层,它简化了在 Spring 应用程序中集成和使用缓存的过程

Spring Cache 不直接实现任何缓存技术,而是定义了一套标准接口和注解,使得开发人员可以轻松地切换底层缓存实现,而无需修改业务逻辑代码

是不是有点似懂非懂,那么使用一个🌰来了解

🌰:一般会将用户相关信息存储到缓存中,为了提高用户信息的查询效率,那么就是如下操作:

@Service
public class UserService {@Autowireprivate UserMapper userMapper;@Autowiredprivate RedisTemplate<String, Object> redisTemplate;//查询用户public User getUserById(Long userId) {String userKey = "userId_" + userId;Uesr user = (User) redisTemplate.opsForValue().get(userKey);if(user != null) {return user;}user = userMapper.getUserById(userId);if(user != null) {redisTemplate.opsForValue().set(userKey, user);return user;}}//更新用户public User updateUser(User user) {userMapper.updateUser(user);String userKey = "userId_" + userId;return (User) redisTemplate.opsForValue().get(userKey);}//删除用户public void deleteUser(Long userId){userMapper.deleteUserById(userId);String userKey = "userId_" + userId.getId();redisTemplate.delete(userKey);}
}

一般都会是这样写,非常容易理解,但是

  1. 代码不够优雅,每次读用户信息查询、新增、修改、删除,都需要调用缓存命令的API,产生很多重复代码
  2. 代码有侵入性,缓存操作和业务逻辑之间的代码耦合度高

侵入性主要体现如下两点:

  • 开发联调阶段,需要去掉缓存,只能注释或者临时删除缓存操作代码,也容易出错;

  • 某些场景下,需要更换缓存组件,每个缓存组件有自己的API,更换成本颇高

那么为了解决上述问题,Spring Cache提供了一种简单的方式来处理缓存,解决了手动管理缓存的复杂性

二、缓存注解

Spring Cache 提供了五个注解:

  • @Cacheable:标记在方法上,表示该方法的结果应该被缓存

根据方法的请求参数对其结果进行缓存,下次同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法

  • @CachePut :更新缓存中的值,通常用于更新缓存而不改变返回值

  • @CacheEvict:根据一定的条件删除缓存

  • @Caching:组合多个缓存注解

  • @CacheConfig:类级别共享缓存相关的公共配置

1、@Cacheable 缓存结果

@Cacheable 注解用于声明一个方法的结果是可缓存的

@Cacheable注解属性介绍

  • value 或 cacheNames: 指定缓存名称,可以是单个或多个

  • key: 指定缓存键的SpEL表达式。

  • condition: 指定缓存的条件SpEL表达式。

  • unless: 指定不缓存的条件SpEL表达式

使用 Spring Cache 的示例代码如下:

    @Cacheable(value = "users", key = "#userId")public User getUser(String userId) {// 从数据库中获取用户信息return findUserById(userId);}

在这个示例中, getUser() 方法的返回值会被缓存,缓存的键是 userId 。每次调用这个方法时,都会检查缓存中是否存在对应的用户信息

多个缓存名称和条件: 

@Cacheable(value = {"users","userInfo"},   //缓存名称key = "#userId"。     //缓存键,使用SpEL表达式condition = "#userId.length() > 3",  // 缓存条件,只有当userId长度大于3时才缓存        unless = "#result == null" // 除非条件,如果结果为null,则不缓存)
public User getUser(String userId) {// 从数据库中获取用户信息return findUserById(userId);
}

2、@CachePut 更新缓存

@CachePut 注解用于在方法执行后更新缓存,属性与 @Cacheable相同

@CachePut(value = "users", key = "#user.id", condition = "#user.age > 18" )
public User updateUser(User user) {// 更新用户信息并缓存return updateUserInDatabase(user);
}

3、@CacheEvict 清除缓存

@CacheEvict 注解用于在方法执行后清除缓存

@CacheEvict 注解属性介绍

  • value 或 cacheNames: 指定缓存名称,可以是单个或多个。

  • allEntries: 清除所有缓存项。

  • condition: 指定清除缓存的条件SpEL表达式

@CacheEvict(value = "users", key = "#userId")
public void deleteUser(String userId) {// 从数据库中删除用户信息deleteUserFromDatabase(userId);
}

4、@Caching 组合缓存(不常用)

@Caching有三个属性:cacheable、put和evict,分别用于指定@Cacheable , @CachePut , @CacheEvict。对于一个数据的变动,更新多个缓存的场景

@Caching 注解用于组合多个缓存操作

@Caching(cacheable = {@Cacheable(value = "userCache", key = "#userId")},put = {@CachePut(value = "userCache", key = "#user.id")},evict = { @CacheEvict(value = "archivedUsers", key = "#userId")}
)
public User updateUser(Long userId, String name) {return findUserByIdAndName(userId, name);
}

5、@CacheConfig 类级别缓存配置(不常用)

@CacheConfig 注解用于在类级别提供缓存相关的共享配置

@CacheConfig 注解说明

  • cacheNames: 指定类中所有缓存操作的默认缓存名称。

  • keyGenerator: 指定默认的缓存键生成器。

  • condition: 指定类中所有缓存操作的默认条件

@CacheConfig(cacheNames = "userCache", keyGenerator = "customKeyGenerator")
public class UserService { // 类中的方法可以使用缓存注解
}

6、@CacheResult 设置缓存超时(不常用)

@CacheResult 是一个非官方的注解,用于替代 @Cacheable,提供额外的配置选项,如超时时间

@CacheResult 注解说明

  • cacheName: 缓存名称。

  • condition: 缓存条件。

  • timeout: 缓存超时时间(毫秒)

@CacheResult(cacheName = "users", timeout = 3600000)  设置超时时间为1小时
public User getUserById(String userId) {// 从数据库中获取用户信息return findUserById(userId);
}

三、使用方式

1、@EnableCaching开启缓存

在主配置类或启动类上添加 @EnableCaching 注解来启用缓存功能

@EnableCaching
public class UserApplication{……
}

2、在方法上添加注解@Cacheable等注解

因此对上面例子🌰使用SpringCache,代码示例如下

@Service
public class UserService {@Cacheable(value = "users", key = "#userId")public User getUserById(String userId) {// 从数据库中获取用户信息return findUserById(userId);}@CachePut(value = "users", key = "#user.id", condition = "#user.age > 18" )public User updateUser(User user) {// 更新用户信息并缓存return updateUserInDatabase(user);}@CacheEvict(value = "users", key = "#userId")public void deleteUser(String userId) {// 从数据库中删除用户信息deleteUserFromDatabase(userId);}
}

在这个示例中, @Cacheable 注解用于缓存 getUser 方法的结果, @CachePut 用于更新用户信息并缓存, @CacheEvict 用于删除缓存中的用户信息

通过这些注解,可以轻松管理缓存,而不需要直接使用 RedisTemplate 

总之,Spring Cache 通过提供高层次的抽象和简化的 API,解决了手动管理缓存所带来的复杂性,使得开发者能够更专注于业务逻辑的实现

四 、优劣势

Spring Cache相对于手动使用RedisTemplate的一些优势和劣势的问题

优势

  1. 简化代码:使用 Spring Cache,您可以通过注解(如 @Cacheable , @CachePut , @CacheEvict )来声明缓存,而不需要手动编写缓存的逻辑。这样可以减少样板代码,提高代码的可读性和可维护性
  2. 统一的缓存抽象Spring Cache 提供了一个统一的 API,可以与多种缓存解决方案(如 Redis, Ehcache, Caffeine 等)集成。这样,可以在不同的缓存实现之间切换,而不需要修改业务逻辑代码
  3. 自动化处理:Spring Cache 可以自动处理缓存的创建、更新和失效。例如,当方法被调用时,Spring Cache 会自动检查缓存中是否存在结果,如果存在则直接返回,不再执行方法
  4. 灵活配置:可以通过配置文件或注解轻松地配置缓存的行为,例如设置缓存的过期时间、最大大小等
  5. 支持方法级别的缓存:通过注解,可以对特定的方法进行缓存,而不需要在整个类中实现缓存逻辑,这样更加灵活

劣势

  • 抽象层次高:隐藏了底层缓存实现的复杂性。这可能导致开发者对缓存的工作原理不够了解,从而在调试和优化时遇到困难
  • 缓存一致性问题:当数据更新时,缓存可能不会立即失效,导致数据不一致。在分布式系统中,确保缓存和数据库之间的一致性是一个挑战

关于缓存一致性问题可以看这篇:Redis与MySQL数据一致性问题的策略模式及解决方案_mysql redis 数据一致性 实现方案-CSDN博客

  • 复杂的配置:对于复杂的缓存策略(例如,基于时间的过期策略、缓存清理等),可能需要额外的配置和代码,增加了实现的复杂性
  • 性能开销:在某些情况下,Spring Cache 的抽象层可能会引入额外的性能开销,尤其是在高并发环境下,可能会对性能产生影响

五、总结

Spring缓存机制通过 @EnableCaching开启,配合  @Cacheable , @CachePut , @CacheEvict 等注解,为Java应用提供了一种声明式管理缓存的方式。这些注解使得缓存配置变得简洁明了,允许开发者轻松实现数据的自动缓存、更新和清除,从而优化应用性能,减少不必要的计算和数据访问开销


推荐文章:使用 Spring Cache 实现缓存,这种方式才叫优雅!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 《第二十八章:性能优化 - 电量优化》
  • Java | Leetcode Java题解之第371题两整数之和
  • 云原生系列 - Nginx(高级篇)
  • 【Linux】分析一段oom及oops报错日志
  • MySQL(面试篇)
  • #Datawhale X 李宏毅苹果书 AI夏令营#3.13.2局部极小值与鞍点批量和动量
  • ‘asyncio‘ with OpenAI API Call Hangs After Extended Run Time
  • 【AI】阿里云AI开发平台PAI:构建智能未来
  • clickhouse 原理详解
  • Spring不是引入了三级缓存,解决了循环依赖的问题吗?
  • 面试中的SEO优化:从基本概念到实用策略
  • JavaSE 面试题 46-50
  • 力扣题解(跳跃游戏II)
  • 关于linux上root连接mysql时遇到的一点小问题以及rsync通过ssh的文件同步传输以及免密码传输的实现
  • C++系列-多态的基本语法
  • Android Volley源码解析
  • ES6系统学习----从Apollo Client看解构赋值
  • Gradle 5.0 正式版发布
  • js写一个简单的选项卡
  • Mithril.js 入门介绍
  • Redux系列x:源码分析
  • Terraform入门 - 3. 变更基础设施
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 从0实现一个tiny react(三)生命周期
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 关键词挖掘技术哪家强(一)基于node.js技术开发一个关键字查询工具
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 我感觉这是史上最牛的防sql注入方法类
  • 怎么将电脑中的声音录制成WAV格式
  • Spring Batch JSON 支持
  • 正则表达式-基础知识Review
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • ​sqlite3 --- SQLite 数据库 DB-API 2.0 接口模块​
  • #HarmonyOS:软件安装window和mac预览Hello World
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (3)llvm ir转换过程
  • (C++哈希表01)
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (Java)【深基9.例1】选举学生会
  • (NSDate) 时间 (time )比较
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (第61天)多租户架构(CDB/PDB)
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (一)十分简易快速 自己训练样本 opencv级联haar分类器 车牌识别
  • (转)人的集合论——移山之道
  • .bat文件调用java类的main方法
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .NET Core中Emit的使用
  • .net MVC中使用angularJs刷新页面数据列表
  • .NET 中使用 Mutex 进行跨越进程边界的同步
  • .NET:自动将请求参数绑定到ASPX、ASHX和MVC(菜鸟必看)
  • .NET单元测试
  • .NET技术成长路线架构图