文档首页> Linux命令> Spring更简单的读取和存储对象

Spring更简单的读取和存储对象

发布时间:2025-12-30 00:12       

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 规范、序列化策略和防击穿方案一起做成一套“可直接落地的缓存规范 + 示例代码”。