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

相关推荐
q***46528 小时前
Win10下安装 Redis
数据库·redis·缓存
p***924810 小时前
深入理解与实战SQL IFNULL()函数
数据库·sql·oracle
q***816412 小时前
MySQL:数据查询-limit
数据库·mysql
p***924812 小时前
DBeaver连接本地MySQL、创建数据库表的基础操作
数据库·mysql
JIngJaneIL13 小时前
社区互助|社区交易|基于springboot+vue的社区互助交易系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·社区互助
晚风吹人醒.13 小时前
缓存中间件Redis安装及功能演示、企业案例
linux·数据库·redis·ubuntu·缓存·中间件
Y***985114 小时前
DVWA靶场通关——SQL Injection篇
数据库·sql
Yawesh_best14 小时前
告别系统壁垒!WSL+cpolar 让跨平台开发效率翻倍
运维·服务器·数据库·笔记·web安全
蒋士峰DBA修行之路14 小时前
实验二十八 SQL PATCH调优
数据库·sql·gaussdb
I***t71614 小时前
一条sql 在MySQL中是如何执行的
数据库·sql·mysql