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

相关推荐
马尔代夫哈哈哈6 小时前
Spring IoC&DI
数据库·sql
液态不合群8 小时前
[特殊字符] MySQL 覆盖索引详解
数据库·mysql
计算机毕设VX:Fegn08958 小时前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
瀚高PG实验室8 小时前
PostgreSQL到HighgoDB数据迁移
数据库·postgresql·瀚高数据库
打码人的日常分享9 小时前
智能制造数字化工厂解决方案
数据库·安全·web安全·云计算·制造
三水不滴9 小时前
Redis 过期删除与内存淘汰机制
数据库·经验分享·redis·笔记·后端·缓存
-孤存-10 小时前
MyBatis数据库配置与SQL操作全解析
数据库·mybatis
2301_8223663510 小时前
使用Scikit-learn构建你的第一个机器学习模型
jvm·数据库·python
万邦科技Lafite12 小时前
一键获取京东商品评论信息,item_reviewAPI接口指南
java·服务器·数据库·开放api·淘宝开放平台·京东开放平台
自可乐12 小时前
Milvus向量数据库/RAG基础设施学习教程
数据库·人工智能·python·milvus