@Cacheable(cacheNames = ChargeConstants.CACHE_PREFIX, cacheManager = "hourCacheManager",
key = "'" + ChargeConstants.ORDER_CACHE_PREFIX + "'+ #root.methodName + ':' + #orderDetailId",
unless = "#result == null")
public ChargeOrderDetailBase queryOrderChargeDetailCache(Long orderDetailId) {
//缓存失效时,查询主库,防止主从不同步。最多5次保护
String orderQueryMasterLimitKey = ChargeConstants.CACHE_PREFIX + "::" + ChargeConstants.ORDER_CACHE_PREFIX + "orderDetailId_MasterLimit:" +orderDetailId;
Integer value = redisTempleUtils.incr(orderQueryMasterLimitKey, 24, TimeUnit.HOURS);
if (value != null && value <= 5) {
return queryOrderDetailFromMasterByDetailId(orderDetailId);
}
RpcResp<ChargeOrderDetailBase> rpcResp = queryOrderChargeDetailWithRpc("", orderDetailId);
return RpcUtil.getData(rpcResp);
}
你这段代码是 Spring Cache 框架中 @Cacheable 注解的典型使用,核心目的是 —— 对标注该注解的方法返回结果做「声明式缓存」,方法执行前先从指定缓存(xx_chahe)中查询:
null 时,将结果存入缓存,后续相同条件的调用可直接复用缓存,提升接口性能。@Cacheable 注解属性逐行解析@Cacheable 是 Spring Cache 的核心注解(声明式缓存,无需手动操作 Redis / 内存缓存),以下拆解每个属性的含义和作用:
| 属性名 | 代码值 | 含义与实现细节 |
|---|---|---|
cacheNames | "xx_cache" | 缓存名称(缓存分组 / 命名空间) 1. 必须指定,Spring 会根据该名称定位对应的缓存区域; 2. 作用:对缓存做逻辑分组(如「用户缓存」「商品缓存」),方便后续批量清理 / 管理; 3. 若底层用 Redis 作为缓存,该名称会作为 Redis Key 的前缀一部分(如 xx_chahe::user_prefix:methodName:123)。 |
cacheManager | "hourCacheManager" | 指定缓存管理器(自定义的「小时级缓存管理器」) 1. Spring Cache 支持多缓存管理器(默认 / 自定义),该属性指定使用名为 hourCacheManager 的 Bean 作为缓存管理器;2. 自定义的 hourCacheManager 通常配置了:- 缓存介质(如 Redis,而非默认内存缓存); - 缓存过期时间(小时级,如 1 小时 / 24 小时,避免缓存永久有效导致脏数据); - 缓存序列化规则(如 JSON 序列化)。 |
key | "'"+Constants.USER_CACHE_PREFIX+"' + #root.methodName + ':' + #userDetailId" | 缓存键(Key)的生成规则(SpEL 表达式),保证缓存键的唯一性,避免不同请求的缓存冲突: 1. 拆解 SpEL 表达式:- '"+Constants.USER_CACHE_PREFIX+"':拼接常量前缀(如 Constants.USER_CACHE_PREFIX = "user:detail:"),单引号包裹表示字符串常量;- #root.methodName:SpEL 内置变量,指当前方法名(如方法名是 getUserDetail,则该部分为 getUserDetail);- #userDetailId:指方法的入参(参数名必须为 userDetailId),如入参值为 123,则该部分为 123;2. 最终生成的 Key 示例: user:detail:getUserDetail:123(唯一标识「getUserDetail 方法查询 userDetailId=123 的结果」)。 |
unless | "#result == null" | 缓存排除条件(「除非」满足该条件,否则缓存): 1. #result:SpEL 内置变量,指方法的返回值;2. 逻辑:仅当方法返回值 != null 时,才将结果存入缓存;若返回值为 null,则不缓存(避免缓存 null 值,导致后续请求直接返回 null,而非重新查询);3. 对比:与 condition 属性相反(condition="#result != null" 等价于 unless="#result == null"),unless 是「排除缓存」,condition 是「条件缓存」。 |
Spring Cache 对标注 @Cacheable 的方法执行时,会按以下步骤处理(核心是「先查缓存,再执行业务,最后缓存结果」):
graph TD
A[调用标注@Cacheable的方法] --> B[根据cacheManager找到hourCacheManager]
B --> C[根据cacheNames=xx_chahe定位缓存区域]
C --> D[解析key表达式生成唯一缓存Key]
D --> E{缓存中是否存在该Key的有效值?}
E -->|是(缓存命中)| F[直接返回缓存值,不执行方法体]
E -->|否(缓存未命中)| G[执行方法体(如查询DB/远程接口)]
G --> H{方法返回值#result是否为null?}
H -->|是(#result=null)| I[不存入缓存,直接返回null]
H -->|否(#result≠null)| J[将返回值存入缓存(Key=解析后的唯一Key)]
J --> K[返回方法执行结果]
缓存命中的核心价值:若缓存中已有该 Key 的值,方法体(如数据库查询 select * from user where id=123)不会执行,直接返回缓存值,大幅减少数据库 / 远程调用开销,提升接口响应速度。
缓存管理器的作用:hourCacheManager是自定义的 Bean,通常在配置类中声明,示例如下(以 Redis 缓存为例):
@Bean("hourCacheManager")
public RedisCacheManager hourCacheManager(RedisConnectionFactory factory) {
// 配置缓存过期时间为1小时
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 小时级过期,对应hourCacheManager
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
Key 唯一性保障:常量前缀 + 方法名 + 参数 的组合,避免不同方法、不同参数的缓存冲突(如 getUserDetail(123) 和 getUserDetail(456) 生成不同 Key,getUserInfo(123) 因方法名不同也生成不同 Key)。
unless 的防御性设计:若方法查询结果为 null(如用户不存在),不缓存 null 值,否则后续查询该不存在的用户时,会一直返回缓存的 null,而非重新查询(避免「缓存穿透」的基础防护)。
该注解适用于「读多写少」的查询场景,比如:
userDetailId 查询用户详情(如 getUserDetail(Long userDetailId));cacheNames 分组 + 自定义 cacheManager,实现不同业务缓存的过期策略、存储介质隔离(如用户缓存 1 小时过期,商品缓存 10 分钟过期);unless="#result == null" 过滤 null 值,避免缓存无效数据,同时减少缓存空间占用。'user:detail:'),否则会被解析为变量名,导致 Key 生成失败;#userDetailId 必须与方法入参名完全一致(若编译时丢失参数名,需用 #arg0 代替,如 #arg0 表示第一个入参);cacheManager = "hourCacheManager" 必须与配置类中 @Bean("hourCacheManager") 的名称一致,否则会找不到缓存管理器,使用默认管理器;@CacheEvict 注解),否则缓存会返回旧数据(脏数据);cacheNames = "xx_chahe" 应为 xx_cache(拼写错误),不影响功能,但建议修正以符合命名规范。@Cacheable 实现声明式缓存,优先从 hourCacheManager 管理的 xx_chahe 缓存中查询,缓存未命中则执行方法体,仅当返回值非 null 时存入缓存;hourCacheManager 控制缓存过期时间(小时级),避免脏数据;unless 过滤 null 值,避免无效缓存;