Redis 缓存模式与注解缓存

目录

Cache-Aside(旁路缓存模式)

[1. 工作流程](#1. 工作流程)

[2. 三大注解](#2. 三大注解)

[3. TTL 配置](#3. TTL 配置)

[Spring Boot 示例代码](#Spring Boot 示例代码)

[1. 启用缓存(主类)](#1. 启用缓存(主类))

[2. Redis 缓存配置(TTL + JSON 序列化)](#2. Redis 缓存配置(TTL + JSON 序列化))

[3. 模拟商品服务(Service)](#3. 模拟商品服务(Service))

[4. 控制器(Controller)](#4. 控制器(Controller))


Cache-Aside(旁路缓存模式)

Cache 即缓存,Aside有在旁边的含义 。在这种模式下,应用程序在数据读取和写入操作时,并非直接与缓存交互,缓存像是处于应用程序数据操作的旁路位置,应用程序主要操作数据库,同时兼顾对缓存的维护,因此得名旁路缓存模式。

1. 工作流程

阶段 读操作(查询) 写操作(更新/删除)
第一步 先查缓存 先写数据库
第二步 缓存没命中就查数据库 再删缓存(而不是更新缓存)
第三步 把查询结果写入缓存 下次查询再自动回填

优点:数据一致性高、实现简单。

注意:写时要删除缓存,不要直接更新缓存,否则容易产生脏数据。

2. 三大注解

注解 作用 时机
@Cacheable 查询时自动查缓存→回源→回写
@CacheEvict 删除缓存(支持 key/全部)
@CachePut 更新缓存但不影响数据库 写(少用)

回源指从数据库重新取数据

回写指把刚从数据库查到的数据写回缓存,让后续的查询可以直接从缓存命中,避免再次访问数据库

@CachePut 少用的核心原因:数据不一致风险

@CachePut 的理念是方法执行后必定更新缓存

但业务逻辑中,方法执行成功 ≠ 数据库更新成功

比如方法执行了,但数据库事务回滚、数据库连接失败、乐观锁冲突等

结果缓存更新了,但数据库没更新,导致数据永久不一致

大部分更新操作需要保证数据库和缓存同时成功

3. TTL 配置

在配置类中设置:

bash 复制代码
RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofMinutes(10));  // 每个缓存项存活 10 分钟

防止缓存爆炸或脏数据长期存在

defaultCacheConfig()

名字里的 default = 默认

CacheConfig = 缓存配置对象

含义是创建一个默认的缓存配置(默认序列化、无过期时间)

然后我们可以在它的基础上再定制(比如加 TTL、加序列化器等)
entryTtl(...)

entry = 每个缓存条目(每个 key)

TTL = Time To Live(生存时间)

含义是为每个缓存条目设置过期时间

这里是说每个缓存项活多久
Duration 是 Java 8 的时间类,用来表示一段持续时间

ofMinutes(10) 表示 10 分钟的时长对象

所以这句返回一个代表10分钟的 Duration 实例

Spring Boot 示例代码

1. 启用缓存(主类)

java 复制代码
@SpringBootApplication
@EnableCaching
public class RedisApplication {
    public static void main(String[] args) {
        SpringApplication.run(RedisApplication.class, args);
    }
}

2. Redis 缓存配置(TTL + JSON 序列化)

java 复制代码
@Configuration
public class RedisCacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {    
        Jackson2JsonRedisSerializer<Object> serializer =
                new Jackson2JsonRedisSerializer<>(Object.class);

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(serializer))
                .entryTtl(Duration.ofMinutes(10));

        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .build();
    }
}

3. 模拟商品服务(Service)

java 复制代码
@Service
public class ProductService {
    private static final Map<Long, Map<String, String>> DB = new HashMap<>();
    static {
        DB.put(1L, Map.of("id", "1", "name", "Phone", "price", "1999"));
        DB.put(2L, Map.of("id", "2", "name", "Laptop", "price", "4999"));
    }

    @Cacheable(cacheNames = "prod", key = "#id")
    public Map<String, String> findById(Long id) {
        System.out.println("📦 查询数据库 id=" + id);
        try { Thread.sleep(300); } catch (InterruptedException ignored) {}
        return DB.get(id);
    }

    @CacheEvict(cacheNames = "prod", key = "#id")
    public void updatePrice(Long id, int newPrice) {
        System.out.println("🧾 更新价格并清除缓存 id=" + id);
        Map<String, String> product = DB.get(id);
        DB.put(id, Map.of(
                "id", product.get("id"),
                "name", product.get("name"),
                "price", String.valueOf(newPrice)
        ));
    }
}

4. 控制器(Controller)

java 复制代码
@RestController
@RequestMapping("/prod")
public class ProductController {
    private final ProductService productService;
    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping("/{id}")
    public Map<String, String> get(@PathVariable Long id) {
        return productService.findById(id);
    }



    @PostMapping("/{id}/price")
    public String update(@PathVariable Long id, @RequestParam int price) {
        productService.updatePrice(id, price);
        return "OK";
    }
}

验证步骤

1. 第一次访问:

GET http://localhost:8080/prod/1

控制台输出:📦 查询数据库 id=1

说明缓存写入成功

2. 第二次访问同一接口:

控制台没有查询数据库字样,则命中缓存

3. 更新:

POST http://localhost:8080/prod/1/price?price=1888

控制台输出清除缓存,再访问 /prod/1 会重新回源

**4.**10 分钟后 TTL 到期,再访问会自动回源刷新

相关推荐
Maverick0610 小时前
Oracle Redo 日志操作手册
数据库·oracle
努力也学不会java10 小时前
【缓存算法】一篇文章带你彻底搞懂面试高频题LRU/LFU
java·数据结构·人工智能·算法·缓存·面试
攒了一袋星辰10 小时前
高并发强一致性顺序号生成系统 -- SequenceGenerator
java·数据库·mysql
W.D.小糊涂10 小时前
gpu服务器安装windows+ubuntu24.04双系统
c语言·开发语言·数据库
云贝教育-郑老师10 小时前
【OceanBase 的多租户架构是怎样的?有什么优势?】
数据库·oceanbase
顶点多余11 小时前
使用C/C++语言链接Mysql详解
数据库·c++·mysql
xiaokangzhe11 小时前
MySQL 数据库操作
数据库·oracle
发际线还在12 小时前
互联网大厂Java三轮面试全流程实战问答与解析
java·数据库·分布式·面试·并发·系统设计·大厂
小王不爱笑13213 小时前
MyBatis 执行流程源码级深度解析:从 Mapper 接口到 SQL 执行的全链路逻辑
数据库·sql·mybatis
山峰哥13 小时前
SQL优化实战:从索引策略到执行计划的极致突破
数据库·sql·性能优化·编辑器·深度优先