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

Spring Cache使用

目录

Spring Cache介绍

依赖引入

配置类和@EnableCaching

RedisCacheConfiguration的常用方法

RedisManager常用方法

注解详解

SpEL 语法总表

@Cacheable(缓存查询)

 @CacheConfig的参数

@Cacheable独特参数sync

sync 参数的作用

@CachePut(更新)

@CacheEvict(清除缓存)

特殊注解

@Caching(组合)


Spring Cache介绍

Spring Cache 是 Spring Framework 提供的一种缓存抽象,旨在简化缓存的使用和集成。它使得开发者可以通过注解方式轻松地对方法的返回结果进行缓存,从而提升应用程序的性能,尤其是在访问频繁但计算开销较大的操作时。
Spring Cache 提供了一个一致的缓存抽象,支持多种缓存技术,比如:

Ehcache
Redis
Guava
Caffeine
JCache (JSR-107)
它提供了注解式的缓存配置,并且能够与现有的缓存解决方案无缝集成。

依赖引入

这里我们只使用到了redis缓存,所以我们这里只引入两个依赖

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

配置类和@EnableCaching

我们在使用springcache前要先开启缓存的功能,此时我们就需要在启动类上面添加注解@EnableCaching注解,当然我们也可以添加在我们的配置类上,他的效果一样的,

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
@EnableCaching
public class RedisCacheConfig {/*** @return* 自定义redis连接的时候的配置,我们也可以写在配置文件里面*/@Beanpublic RedisConnectionFactory redisConnectionFactory(){RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();//配置单机模式下的redis,指定Redis服务器的地址端口,密码等基本信息config.setHostName("localhost");//设置访问地址config.setPort(6379);//设置端口config.setDatabase(1);//设置使用1号数据库,默认是使用0号数据库return new LettuceConnectionFactory(config);//将配置传给LettuceConnectionFactory生成一个工厂实例,这个工厂用于管理连接池和低层网络通信}@Bean@Primary//设置为默认的缓存管理器,当我们设置多个缓存管理器的时候,若不设置默认的缓存管理器是会报错的public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()//默认配置,下面是在默认配置的基础之上加功能.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))//设置key的序列化.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))//设置value的序列化.disableCachingNullValues();//禁止设置空值,防止缓存穿透;//构建RedisCacheManagerreturn RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();}
}

这段代码是设置缓存配置的

首先是开启spring缓存功能的注解@EnableCaching,他允许在应用中使用缓存注解,如@Cacheable,@CacheEvict等

然后是redisConnectionFactory,他是用来创建一个RedisConnectionFactory这个类的,这个类是一个连接工厂,他主要是用于创建和管理与Redis服务器连接的,他的作用是提供Redis客户端与Spring应用之间的桥梁,使得应用能够通过连接工厂与Redis交互。简单说RedisConnectionFactory就是负责与Redis连接,使得在项目中能够操作Redis里面的数据

RedisStandaloneConfiguration是用来在单节点中设置连接Redis的基本参数的,如Redis的服务器的主机,端口和使用的数据库等。

然后再new一个客户端的实现类,这里是LettuceConnectionFactory,这是基于 Lettuce 客户端的实现,Lettuce 是一个支持异步和反应式编程的 Redis 客户端,通常用于高性能、非阻塞的 Redis 操作。

@primary注解的作用就是,当我们设置多个缓存的管理器的时候,使用@Primary注解用来指定在方法上面的这个缓存管理器作为默认的管理器,当有多个管理器而没有指定默认管理器的话,在启动项目的时候会抛出异常

CacheManager是Spring缓存的核心接口,管理缓存的创建和存取

RedisCacheConfiguration是用来配置Redis缓存的类,他主要是设置我们在对Redis里面的数据操作的时候的一些配置,比如说key和value的序列化,RedisCacheConfiguration.defaultCacheConfig()是用来创建一个默认的缓存配置,我们可以通过链式调用来设置其他的一些配置

然后再通过RedisManager里面的CacheDefaults方法来设置我们自定义的配置

RedisCacheConfiguration的常用方法

entryTtl(Duration ttl)Duration.ofMinutes(30)设置缓存的默认过期时间(TTL),所有缓存条目在指定时间后自动过期。控制缓存自动清理,避免数据陈旧:
.entryTtl(Duration.ofHours(1))
disableCachingNullValues()禁止缓存空值,防止缓存穿透(恶意请求频繁查询无效数据导致数据库压力)。保护数据库资源:
.disableCachingNullValues()
serializeKeysWith(SerializationPair)StringRedisSerializer键序列化方式,通常用字符串序列化保证键可读性。键名可读性要求高:
.serializeKeysWith(stringSerializer)
serializeValuesWith(SerializationPair)GenericJackson2JsonRedisSerializer值序列化方式,推荐 JSON 格式支持复杂对象存储。跨语言兼容或调试需求:
.serializeValuesWith(jsonSerializer)
prefixCacheNameWith(String prefix)"prod:cache:"统一缓存键前缀,避免多环境或应用共用一个 Redis 时键冲突。多环境隔离:
.prefixCacheNameWith("dev:cache:")
disableKeyPrefix()禁用键前缀,直接使用原始缓存名作为键前缀。需要简洁键名时(慎用):
.disableKeyPrefix()
withEntryTtl(Duration ttl)Duration.ofSeconds(60)链式调用设置 TTL(与 entryTtl 等效,用于覆盖父配置)。组合配置时覆盖默认值:
.withEntryTtl(Duration.ofHours(2))
computePrefixWith(Function<String, String> prefixFunction)name -> "custom:" + name + ":"动态生成键前缀,根据缓存名称自定义前缀规则。灵活前缀规则:
.computePrefixWith(name -> "app_" + name + "::")

RedisManager常用方法

getCache(String name)name:缓存名称
返回 Cache 对象
根据缓存名称获取对应的 Cache 实例。若缓存不存在且允许动态创建(默认允许),则按默认配置创建新缓存。手动操作缓存时使用:
cacheManager.getCache("users").put("key", value)
getCacheNames()返回 Set<String>获取所有已注册的缓存名称集合(包括动态创建的缓存)。遍历所有缓存或检查缓存是否存在:
cacheManager.getCacheNames().contains("orders")
isTransactionAware()返回 boolean检查缓存管理器是否启用了事务支持(即构建时调用了 .transactionAware())。调试事务同步问题:
if(cacheManager.isTransactionAware()) { ... }
getStatistics()返回 CacheStatistics(需启用统计)获取缓存统计信息(如命中次数、未命中次数),需在构建时启用统计(.enableStatistics())。性能监控和优化:
cacheManager.getCache("users").getStatistics().getHitRatio()
defineConfiguration(String cacheName, RedisCacheConfiguration config)cacheName:缓存名称
config:配置
动态添加或覆盖指定缓存的配置(如运行时按需创建新缓存)。动态扩展缓存策略:
cacheManager.defineConfiguration("newCache", customConfig)
isAllowInFlightCacheCreation()返回 boolean检查是否允许自动创建未显式配置的缓存(默认 true)。控制缓存创建的严格性:
if(!cacheManager.isAllowInFlightCacheCreation()) { ... }

除了RedisManager之外还有CaffeineCacheManager等缓存管理器,其他的可自行网上是搜索

注解详解

我们下面会讲解到@Cacheable、@CachePut、@CacheEvict ,@Caching这四个注解

而@Cacheable、@CachePut、@CacheEvict的通用参数

参数名类型默认值作用示例
valueString[]缓存名称(逻辑容器),可指定多个。等价于 cacheNames@Cacheable(value = "users")
cacheNamesString[]同 value,语义更明确。@Cacheable(cacheNames = "users")
keyString缓存键的 SpEL 表达式,用于动态生成键值。@Cacheable(key = "#userId")
keyGeneratorStringSimpleKeyGenerator指定自定义的键生成器 Bean 名称(覆盖 key)。@Cacheable(keyGenerator = "myKeyGenerator")
conditionString执行前条件(SpEL 表达式),满足条件时缓存操作生效。@Cacheable(condition = "#userId.length() > 5")
unlessString执行后条件(SpEL 表达式),满足条件时不缓存结果@Cacheable(unless = "#result == null")
cacheManagerString默认 CacheManager指定使用的缓存管理器 Bean 名称(多缓存管理器时使用)。@Cacheable(cacheManager = "redisCacheManager")

SPEL语法

SpEL 语法总表

类别语法/符号说明缓存注解示例
核心变量
#root.methodName当前方法名获取被调用方法的名称@Cacheable(key = "#root.methodName + ':' + #userId")
#root.target目标对象实例获取方法所属的类实例@Cacheable(key = "#root.target.getClass().name")
#root.args方法参数数组通过索引访问参数,如 #root.args[0]@Cacheable(key = "#root.args[0].id")
#root.targetClassroot对象当前被调用的目标对象的类#root.targetClass
#result方法返回值仅在 @CachePut 和 @Cacheable 的 unless 中可用@Cacheable(unless = "#result == null")
#参数名方法参数直接引用需编译保留参数名(或使用 @Param 注解)@Cacheable(key = "#userId + '_' + #type")
运算符
算术运算+-*/%数值计算@Cacheable(key = "#id * 1000")
逻辑运算andornot&&, `布尔逻辑判断@Cacheable(condition = "#id > 0 && #isActive")
比较运算==!=<><=>=值比较@Cacheable(unless = "#result.age < 18")
正则匹配matches正则表达式匹配@Cacheable(condition = "#email matches '^\\w+@\\w+\\.com$'")
安全导航?.避免空指针(如 #result?.address?.city@Cacheable(unless = "#result?.address == null")
集合操作
集合元素访问list[0]map['key']访问集合或 Map 中的元素@Cacheable(key = "#userList[0].id")
类型转换T(全限定类名)调用类的静态方法或引用常量@Cacheable(key = "T(java.util.UUID).randomUUID().toString()")
集合投影集合.![属性]提取集合中每个元素的属性组成新集合@Cacheable(key = "#users.![id]")
集合选择集合.?[条件]过滤符合条件的集合元素@Cacheable(condition = "#users.?[age > 18].size() > 0")
特殊符号
#变量前缀引用上下文变量(如 #userId@Cacheable(key = "#userId")
T()类型操作符访问类静态方法或常量(如 T(java.lang.Math).PI@Cacheable(condition = "#role == T(com.example.Role).ADMIN")
'' 或 ""字符串定界符定义字符串字面量@Cacheable(key = "'constant_key:' + #userId")

我们在使用root对象的时候,可以将#root省略,因为spring默认使用的就是root对象的书写

@Cacheable(缓存查询)

添加在方法上面,在方法执行前先查询缓存中是否有对应的数据,若有数据则直接返回缓存数据,若没有缓存数据缓存数据则调用方法并将最后方法要返回的值保存到缓存中

如@Cacheable(key = "targetClass + methodName +#p0")

使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:
@Cacheable(value="users", key="#id")
@Cacheable(value="users", key="#p0")

这里先给出实体类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {private String username;private String password;
}

注意这里实体类要实现Serializable接口,否则会报错

controller层方法

import com.part1.Entity.User;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class TestController {@GetMapping("/login")@Cacheable(value = "User", key = "#username")public User login(@RequestParam String username) {return new User("张三", "123456");}
}

注意到我们这里定义了一个接口使用了@Cacheable注解来查询缓存里面的数据,很多人可能并不知道value到底是来干什么的,其实他就是定义了一个命名空间,当我们同过上面的方法来设置缓存的时候,其实他是通过value::key(在注解里面的参数)来设置的,value::key这一整个就是一个在redis的key,而我们在注解里面定义的value就是一个缓存的区域,你可以把它理解为一个大文件夹,所有通过在注解里面设置相同的value之后,都会放在一个大文件夹里面,如下图

我设置了两次value一个是User一个是User1,他们在视觉上像是放在两个不同的文件夹下面的,但实际上他们在redis底层是没有这样子做的,他是为了更好好的让我们能看而已,所以在注解里面的value实际上就是指定缓存的名称,也可以称为缓存的区域,他只是逻辑上的分组而已,通常由缓存的名称来区分不同的数据存储区域。

而这里的动态变量有很多种写法比如

1如下面的#user.id就是取拿到形参user里面的id,这里可以通过形参的名字来指定要设置的值

2可以通过方法的返回值来获取对应的信息,就如下面的#result.id就是设置key这个值为返回值的id

3通过p的形式来获取下面的p0地表着形参的第一个参数依次类推

4通过a的形式来获取,如下面的a0就是获取形参的第一个形参

5通过root.arg[0]获取形参的第一个,依次类推如果我们在一个类里面都是将数据放在同个缓存的区域里面我们就能将value放在类上面,减少代码的冗余如下图

import com.part1.Entity.User;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@CacheConfig(cacheNames = "User1")
public class TestController {@GetMapping("/login")@Cacheable(key = "#username")public User login(@RequestParam String username) {return new User("李四", "123456");}
}

我们还可以指定多个value.这样他们就会将方法的数据放在多个数据存储区域空间里面,但是如果你在@Cacheable里面也设置了value的话会以@Cacheable里面的来设置

import com.part1.Entity.User;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@CacheConfig(cacheNames = {"User1","User"})
public class TestController {@GetMapping("/login")@Cacheable(key = "#username")public User login(@RequestParam String username) {return new User("李四", "123456");}
}

 @CacheConfig的参数

属性说明示例
cacheNames指定缓存的名称,可以是一个或多个缓存名称。@CacheConfig(cacheNames = "books")
keyGenerator指定自定义的 KeyGenerator 用于生成缓存的键。@CacheConfig(keyGenerator = "customKeyGenerator")
cacheManager指定 CacheManager,用于选择或定义缓存的管理器。@CacheConfig(cacheManager = "myCacheManager")
cacheResolver指定 CacheResolver,用于解析缓存的解决方案。@CacheConfig(cacheResolver = "myCacheResolver")

@Cacheable独特参数sync

sync 参数的作用

当 sync = true 时,启用同步锁机制,确保在缓存未命中(Cache Miss)时,只有一个线程执行方法体,其他并发线程会阻塞并等待结果,避免重复计算或多次访问底层资源(如数据库)。
主要解决以下问题:

  • 缓存击穿(Cache Breakdown):高并发请求同时查询同一个未缓存的键,导致大量请求穿透到数据库。

  • 重复计算:多个线程同时执行方法体,浪费计算资源(尤其是耗时操作)。

@CachePut(更新)

@CachePut他主要是根据请求的参数来对其方法的返回结果进行缓存,但是与@Cacheable不同的是,他每次都会出发方法调用,注意这里注解里面的key合value与我们在@Cahceable里面设置的一样才行,刚刚我们已经往缓存里面设置了数据了,所以现在我们调用CachePut来更新数据

import com.part1.Entity.User;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@CacheConfig(cacheNames = {"User1","User"})
public class TestController {@GetMapping("/login")@CachePut(key = "#username")public User login(@RequestParam String username) {return new User("王五", "123456");}
}

查看结果

我们成功的将里面的username设置为了王五,注意对应@CachePut注解更新数据来说,他是将原有的数据先删除然后再插入的数据,而不是在原来的数据上修改的

@CacheEvict(清除缓存)

特殊注解

属性解释示例
allEntries是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存@CachEvict(value=”testcache”,allEntries=true)
beforeInvocation是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存@CachEvict(value=”testcache”,beforeInvocation=true)

上面的特殊注解里面allEntries是用于清空所有缓存内容(在方法执行之后),beforeInvocation是用于再方法执行之前就清空的,他会根据我们指定的value来删除指定的存储空间里面的数据,或者我们可以搭配着condition来删除

import com.part1.Entity.User;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@CacheConfig(cacheNames = {"User1","User"})
public class TestController {@GetMapping("/login")@CacheEvict(key = "#username")public User login(@RequestParam String username) {return new User("王五", "123456");}
}

此时就能够发现他里面的数据就被清除了

@Caching(组合)

有时候我们可能会再一个方法上面使用同一种注解多次,但是spring不允许我们直接再一个方法上面添加多个同种的注解,所以我们可以使用@Caching

形如这样的

import com.part1.Entity.User;
import org.springframework.cache.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@CacheConfig(cacheNames = {"User1","User"})
public class TestController {@GetMapping("/login")@Caching(cacheable = {@Cacheable(........)@Cacheable(.......)},put = {@CachePut(......)})public User login(@RequestParam String username) {return new User("王五", "123456");}
}

最后

本人的第十三篇博客,以此来记录我的后端java学习。如文章中有什么问题请指出,非常感谢!!!

相关文章:

  • Python表达式全解析:从基础到高级
  • Java多语言DApp质押挖矿盗U源码(前端UniApp纯源码+后端Java)
  • 算法每日一题 | 入门-顺序结构-数字反转
  • c++回调函数
  • Python 线程安全机制:Lock、RLock、Semaphore 的使用场景与最佳实践
  • PyTorch_创建01张量
  • Java虚拟线程基础介绍
  • 突破认知边界:神经符号AI的未来与元认知挑战
  • JAVA刷题记录: 递归,搜索与回溯
  • 纯Java实现STDIO通信的MCP Server与客户端验证
  • 普通 html 项目引入 tailwindcss
  • Go小技巧易错点100例(二十八)
  • 应用层自定义协议序列与反序列化
  • 数据赋能(209)——质量管理——时效性原则
  • 模型测试报错:有2张显卡但cuda.device_count()显示GPU卡数量只有一张
  • 昇腾的CANN是什么?跟英伟达CUDA的有什么联系和区别?【浅谈版】
  • 智能决策支持系统的系统结构:四库架构与融合范式
  • P1537 数字反转(升级版)详解
  • 【unity游戏开发入门到精通——UGUI】整体控制一个UGUI面板的淡入淡出——CanvasGroup画布组组件的使用
  • 深入探索 AAC 编码原理与 ADTS 格式:音频世界的智慧结晶
  • 抗战回望15︱《五月国耻纪念专号》:“不堪回首”
  • 党政机关停车场免费、食堂开放,多地“五一”游客服务暖心周到
  • 解放日报:人形机器人新赛道正积蓄澎湃动能
  • 中国海油总裁:低油价短期影响利润,但也催生资产并购机会
  • 坚持科技创新引领,赢得未来发展新优势
  • “乐购浦东”消费券明起发放,多个商家同期推出折扣促销活动