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 到期,再访问会自动回源刷新

相关推荐
萧曵 丶几秒前
MySQL InnoDB 实现 MVCC 原理
数据库·mysql·mvcc
ss2732 分钟前
ruoyi 新增每页分页条数
java·数据库·mybatis
oMcLin13 分钟前
如何在 Debian 10 上通过配置 Redis 集群的持久化选项,提升高可用性缓存系统的容错性与性能?
redis·缓存·debian
万粉变现经纪人15 分钟前
如何解决 pip install mysqlclient 报错 ‘mysql_config’ not found 问题
数据库·python·mysql·pycharm·bug·pandas·pip
lkbhua莱克瓦2426 分钟前
进阶-SQL优化
java·数据库·sql·mysql·oracle
石小千28 分钟前
Myql binlog反向解析成sql
数据库·sql
alonewolf_9935 分钟前
MySQL 8.0 主从复制原理深度剖析与实战全解(异步、半同步、GTID、MGR)
数据库·mysql·adb
八九燕来1 小时前
django + drf 多表关联场景下的序列化器选型与实现逻辑
数据库·django·sqlite
一路向北⁢1 小时前
短信登录安全防护方案(Spring Boot)
spring boot·redis·后端·安全·sms·短信登录