示例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对应的返回结果一致,那么缓存就能正确命中。
⚠️ 潜在问题与改进建议
-
参数名解析
SpEL 中的#activityInstanceRequest依赖于方法参数名。如果编译时没有保留参数名(即未使用-parameters编译选项),Spring 将无法识别这个名称,导致表达式失效。
建议:使用基于索引的写法(如#p0或#a0)更安全:java
@Cacheable(value = "activityInstanceRequests", key = "#p0.getActivityId()") // 或 @Cacheable(value = "activityInstanceRequests", key = "#a0.activityId")
-
空值处理
如果activityId可能为null,缓存键会变为null,某些缓存实现(如 Caffeine)会抛出异常。建议:确保activityId不会为空,或使用#p0?.activityId做空安全处理(返回null时缓存不会生效)。 -
直接使用属性访问
SpEL 支持直接访问属性(会自动调用 getter),因此#p0.activityId比显式调用getActivityId()更简洁:java
@Cacheable(value = "activityInstanceRequests", key = "#p0.activityId")
-
与其他方法冲突
如果同一个缓存区域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的情况。
🔧 更好的做法
-
确保
activityId不为null:如果业务上activityId不可能为null,那么直接使用#p0.activityId或#p0?.activityId都可以,但加上?.是更安全的选择。 -
结合
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时,方法不会被缓存(但每次都会执行)。 -
如果编译时保留了参数名,使用参数名更清晰:
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 非空后,它就能成为可靠且健壮的缓存键写法。
1773

被折叠的 条评论
为什么被折叠?



