Spring Cache 开启缓存示例,key 使用 SpEL 表达式

示例1:静态 key

    @Cacheable(value = "capitalOptions", key = "'all'")
    public CapitalAllOptionDTO queryCapitalAllOption() {

示例2:动态 key,一个参数

    @Cacheable(value = "users", key = "#userName")
    public User queryByUserName(String userName) {
        return userMapper.selectByUserName(userName);
    }

示例3:动态 key,一个参数,可能为null,SpEL 映射

使用 SpEL 表达式将 null 映射为占位符,将null 转换为一个非 null 对象,例如字符串 "null"

    @Cacheable(value = "acceptGroupVerifyTypes", key = "#acceptGroupId ?: 'null'")
    public List<SelectOptions> queryVerifyTypeListByAcceptGroupId(Integer acceptGroupId) {
        if (acceptGroupId == null) {
            return new ArrayList<>();
        }

        return selectOptionsMapper.selectVerifyTypeListByAcceptGroupId(acceptGroupId);
    }

示例4:动态 key,多个参数

    @Cacheable(value = "users", key = "#commandType + '-' + #id")
    public List<User> queryUserListByCommand(String commandType, Integer id) {

示例5:动态 key,使用 SpEL 调用静态方法

    @Cacheable(value = "tasks", key = "T(com.weiyu.util.PublicUtils).getUserName()")
    public List<Task> queryTask() {
        return taskMessageMapper.selectTask(PublicUtils.getUserName());
    }

示例6:动态 key,对象单个属性,并且不能为空

    @Cacheable(value = "startActivityInstances", key = "#p0?.activityId", unless = "#p0?.activityId == null")
    private ActivityInstance getStartActivityInstance(ActivityInstanceRequest activityInstanceRequest) {

示例7:动态key,对象多个属性,并且都不能为空

    @Cacheable(value = "rejectOptions",
            key = "#p0?.activityId + '-' + #p0?.activityInstanceId + '-' + #p0?.workflowInstanceId",
            unless = "#p0?.activityId == null || #p0?.activityInstanceId == null || #p0?.workflowInstanceId == null")
    public ActivityInstanceRejectOptionDTO queryRejectOption(ActivityInstanceRequest activityInstanceRequest) {

📌 推荐写法(兼顾安全与清晰)

这样既避免了 SpEL 空指针,又防止了 null 作为缓存键。但注意:unless 表达式中的 ?. 也是可选的,因为 #p0 不会为 null(参数非 null),但为了一致性可以保留。

p0是指第一个参数


可以这样设置缓存 key,但需要注意几个关键点:

✅ 为什么可行?

  • @Cacheable(value = "activityInstanceRequests", key = "#activityInstanceRequest.getActivityId()") 使用了 SpEL 表达式,会调用参数的 getActivityId() 方法作为缓存键。

  • 如果 ActivityInstanceRequest 对象在每次查询时都提供了正确的 activityId,且相同 activityId 对应的返回结果一致,那么缓存就能正确命中。

⚠️ 潜在问题与改进建议

  1. 参数名解析
    SpEL 中的 #activityInstanceRequest 依赖于方法参数名。如果编译时没有保留参数名(即未使用 -parameters 编译选项),Spring 将无法识别这个名称,导致表达式失效。
    建议:使用基于索引的写法(如 #p0 或 #a0)更安全:

    java

    @Cacheable(value = "activityInstanceRequests", key = "#p0.getActivityId()")
    // 或
    @Cacheable(value = "activityInstanceRequests", key = "#a0.activityId")
  2. 空值处理
    如果 activityId 可能为 null,缓存键会变为 null,某些缓存实现(如 Caffeine)会抛出异常。建议:确保 activityId 不会为空,或使用 #p0?.activityId 做空安全处理(返回 null 时缓存不会生效)。

  3. 直接使用属性访问
    SpEL 支持直接访问属性(会自动调用 getter),因此 #p0.activityId 比显式调用 getActivityId() 更简洁:

    java

    @Cacheable(value = "activityInstanceRequests", key = "#p0.activityId")
  4. 与其他方法冲突
    如果同一个缓存区域 activityInstanceRequests 被其他方法使用,且 key 生成逻辑不同,可能导致数据混乱。建议为该区域单独命名,或确保所有方法的 key 生成逻辑一致。

最终推荐写法

java

@Cacheable(value = "activityInstanceRequests", key = "#p0.activityId")
public ActivityInstanceRejectOptionDTO queryRejectOption(ActivityInstanceRequest activityInstanceRequest) {
    // ...
}

这样既避免了参数名解析问题,又简洁明了。


你的写法 @Cacheable(value = "activityInstanceRequests", key = "#p0?.activityId") 确实考虑了空安全(如果 activityId 为 null,表达式返回 null 而不是抛出异常),这比直接 #p0.activityId 更健壮。但它是否“最好”,还需结合以下分析:

✅ 优点

  • 使用参数索引 #p0 避免了参数名解析问题,兼容性好。

  • 安全导航操作符 ?. 防止了 activityId 为 null 时 SpEL 解析抛出空指针异常。

⚠️ 潜在问题

  • null 作为缓存键:即使 SpEL 返回了 null,大多数缓存管理器(如 Caffeine、Redis)不允许 null 作为键,会抛出 NullPointerException 或类似异常。因此,仅靠 ?. 并不能完全避免错误,你需要确保 activityId 永远不为 null,或者在注解中显式排除 null 的情况。

🔧 更好的做法

  1. 确保 activityId 不为 null:如果业务上 activityId 不可能为 null,那么直接使用 #p0.activityId 或 #p0?.activityId 都可以,但加上 ?. 是更安全的选择。

  2. 结合 condition 或 unless 过滤

    java

    @Cacheable(value = "activityInstanceRequests", key = "#p0.activityId", condition = "#p0.activityId != null")

    java

    @Cacheable(value = "activityInstanceRequests", key = "#p0.activityId", unless = "#p0.activityId == null")

    这样当 activityId 为 null 时,方法不会被缓存(但每次都会执行)。

  3. 如果编译时保留了参数名,使用参数名更清晰:

    java

    @Cacheable(value = "activityInstanceRequests", key = "#request?.activityId")

📌 推荐写法(兼顾安全与清晰)

java

@Cacheable(value = "activityInstanceRequests", key = "#p0?.activityId", unless = "#p0?.activityId == null")

这样既避免了 SpEL 空指针,又防止了 null 作为缓存键。但注意:unless 表达式中的 ?. 也是可选的,因为 #p0 不会为 null(参数非 null),但为了一致性可以保留。

最终结论

你提出的写法是一个很好的改进,但并不是“最好”的完整形态。结合 unless 或业务逻辑确保 activityId 非空后,它就能成为可靠且健壮的缓存键写法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值