Spring更简单的读取和存储对象
Spring 更简单的读取与存储对象:用Spring Cache把复杂度“降维”🙂
在 Spring 体系里,如果你想把“存对象 / 取对象”做得更简单、代码更少、并发更稳,最务实的方案通常不是自己封装一堆 get/set,而是直接用 Spring Cache 抽象:你只关心业务方法,缓存(无论底层是 Redis、Caffeine 还是别的)交给框架治理。这样做的核心价值是:代码收敛、一致性更强、可观测性更好。✅

1)最简单的“存/取对象”方式:@Cacheable / @CachePut / @CacheEvict
1.1 读取:方法级缓存(命中就不执行方法)
@Cacheable(cacheNames = "user:detail", key = "#userId")
public UserDTO getUserDetail(Long userId) {
// 这里一般是查数据库/远程接口
return userRepository.findById(userId)
.map(UserDTO::from)
.orElseThrow(() -> new RuntimeException("not found"));
}
解释(这段代码做了什么)
@Cacheable:先查缓存;命中则直接返回,方法体不会执行。cacheNames = "user:detail":相当于缓存命名空间,便于治理与隔离。key = "#userId":缓存 Key 规则;这里用 userId 做唯一键,定位精准。- 返回
UserDTO:推荐缓存“对外输出对象”,避免把实体对象的懒加载、代理等复杂性带进缓存层。
1.2 写入:强制刷新缓存(方法执行后一定写缓存)
@CachePut(cacheNames = "user:detail", key = "#userId")
public UserDTO refreshUserDetail(Long userId) {
return userRepository.findById(userId)
.map(UserDTO::from)
.orElseThrow(() -> new RuntimeException("not found"));
}
解释(它与 @Cacheable 的关键差异)
@CachePut:不会先查缓存,一定执行方法,并用返回值更新缓存。- 适用场景:后台强制刷新、数据修复、热更新、主动预热等。✅
1.3 删除:更新数据后,清理旧缓存(避免脏读)
@CacheEvict(cacheNames = "user:detail", key = "#userId")
public void updateUserName(Long userId, String newName) {
userRepository.updateName(userId, newName);
}
解释(为什么先改库再删缓存也行?)
- 这段是典型“写后清缓存”。写库完成后清理缓存,下一次读取会触发
@Cacheable重新加载。 - 若你追求更强一致性,可在同一事务提交后再清缓存(需要配合事务事件)。但大多数业务场景,这种写后清理已经足够稳。
2)让对象“能存能取”的关键:选择合适的序列化(别让缓存变成事故现场)⚠️
如果底层是 Redis,最省心的策略是:缓存内容用 JSON 序列化,避免 JDK 序列化带来的兼容性与可读性问题。
@Bean
public RedisCacheConfiguration redisCacheConfiguration(ObjectMapper objectMapper) {
// 使用 JSON 序列化缓存值
GenericJackson2JsonRedisSerializer serializer =
new GenericJackson2JsonRedisSerializer(objectMapper);
return RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(serializer)
)
.entryTtl(Duration.ofMinutes(10));
}
解释(逐步拆解)
GenericJackson2JsonRedisSerializer:把对象序列化为 JSON,跨版本更友好,排障更直观。serializeValuesWith(...):指定缓存 value 的序列化方式,否则你可能拿到不可读的二进制。entryTtl(10分钟):设置统一 TTL,避免“永久缓存”导致脏数据长期存在。ObjectMapper:由 Spring 管理,统一你的时间格式、字段策略等,避免同一对象在不同模块序列化不一致。
3)更“简单粗暴”的对象存取:直接用RedisTemplate(适合脚本化/工具化场景)
如果你不想走注解体系,只想快速 get/set 一个对象,也可以走模板操作:
public void saveUser(UserDTO user) {
String key = "user:detail:" + user.getId();
redisTemplate.opsForValue().set(key, user, Duration.ofMinutes(10));
}
public UserDTO loadUser(Long userId) {
String key = "user:detail:" + userId;
return (UserDTO) redisTemplate.opsForValue().get(key);
}
解释(为什么这方案“快”,但治理弱一点)
opsForValue().set/get:最直观的 KV 操作,学习成本最低。Duration.ofMinutes(10):写入时就带 TTL,减少遗忘概率。- 但缺点也明确:Key 规则、缓存击穿保护、统一治理都要你自己兜底。适合工具类、小模块,不适合大规模团队协作的核心链路。
4)推荐决策:你到底该选哪种?(务实版本)✅
| 方案 | 代码量 | 治理能力 | 适用场景 |
|---|---|---|---|
| Spring Cache 注解 | 低 | 高 | 业务主链路、团队协作、需要统一策略 |
| RedisTemplate 直写 | 中 | 中/低 | 工具化、临时缓存、简单 KV 模块 |
一句话结论:想“更简单”且“更像产品能力”,优先用 Spring Cache;想“快速落地、少框架约束”,用 RedisTemplate。🙂
如果你告诉我你当前缓存底层是 Redis 还是 Caffeine,以及对象大小/更新频率,我可以把 TTL、Key 规范、序列化策略和防击穿方案一起做成一套“可直接落地的缓存规范 + 示例代码”。