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

相关推荐
l1t25 分钟前
利用DeepSeek优化SQLite求解数独SQL用于DuckDB
开发语言·数据库·sql·sqlite·duckdb
lcanfly28 分钟前
Mysql作业5
android·数据库·mysql
rit843249933 分钟前
在Ubuntu上配置Nginx实现开机自启功能
数据库·nginx·ubuntu
海绵啵啵呀1 小时前
SQL plus中解决上下键找历史命令的工具--rlwrap命令行工具
数据库·sql
Elastic 中国社区官方博客1 小时前
使用 Mastra 和 Elasticsearch 构建具有语义回忆功能的知识 agent
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
老邓计算机毕设1 小时前
SSM危险品运输车辆信息管理系统b2z1o(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·ssm 框架
MuYiLuck1 小时前
redis持久化与集群
java·数据库·redis
埃泽漫笔2 小时前
Redis性能优化避坑指南
redis
卓码软件测评2 小时前
软件数据库测试:【数据库质量保障:从单元测试到性能优化】
运维·数据库·测试用例·压力测试
LilySesy2 小时前
ABAP+在select的时候,可以A=B A=C B=C这样子JOIN吗?
数据库·sql·ai·excel·sap·abap