点评--day03--2. 2 封装Redis工具类

5 阅读3分钟

📚 2.2 商户缓存 - 8. 封装 Redis 工具类学习文档

一、 为什么要封装工具类?(核心动机)

在实际的企业级项目中,需要缓存的数据远不止“商户详情”这一种。可能还有“活动信息”、“商品详情”、“用户配置”等等。

  • 痛点: 难道每次遇到新的业务对象,我们都要把“查 Redis -> 查不到 -> 查 MySQL -> 解决穿透(存空值) -> 解决击穿(加锁/逻辑过期)”这几百行防守代码重新写一遍吗?
  • 解法: 遵循 DRY (Don't Repeat Yourself) 原则。我们需要将这些共性的、与具体业务无关的缓存逻辑剥离出来,封装成一个通用的 CacheClient 工具类。各个 Service 层只需要调用这个工具类即可。

二、 工具类需要具备的 4 大核心能力

一个合格的 Redis 缓存工具类,通常需要对外暴露以下几个核心方法:

  1. 普通缓存写入: * 将任意 Java 对象序列化为 JSON 并存入 Redis,同时设置 TTL 过期时间。
  2. 逻辑过期缓存写入: * 将任意 Java 对象封装成带有 expireTime 的特制对象(如 RedisData),序列化为 JSON 存入 Redis(专门为了解决缓存击穿)。
  3. 防穿透查询(通用版): * 根据指定的 Key 查询缓存,如果不存在,自动去数据库查询,并自带“缓存空值”的防御机制
  4. 防击穿查询(通用版): * 根据指定的 Key 查询缓存,如果遇到高并发热点失效,自动基于“逻辑过期”或“互斥锁”机制去异步重建缓存,并返回旧数据。

三、 核心技术难点解析:Java 泛型与函数式编程

封装这个工具类最大的难点在于:工具类怎么知道去哪里查数据库?

工具类里是没有 shopMapper.selectById() 的,因为它不知道当前查的是商户还是商品。

为了实现“通用”,我们需要用到 Java 8 的两个高级特性:

1. 泛型 (Generics) <R, ID>

因为我们不知道最终要返回什么类型的对象(Shop 还是 User),也不知道查询条件是什么类型(Long 还是 String),所以方法必须声明泛型。

  • R 代表 Return,即最终返回的实体类。
  • ID 代表查询条件的类型。

2. 函数式接口 (Function<ID, R>)

既然工具类不知道怎么查数据库,那我们就把“查数据库的逻辑”当作参数传给工具类

  • Function<ID, R> 的含义是:给我一个 ID 类型的参数,我会还给你一个 R 类型的结果。
  • 实战演练: 在 Service 层调用工具类时,只需要传入一行 Lambda 表达式即可,例如:id -> getById(id)。工具类内部在需要查数据库时,调用 dbFallback.apply(id) 即可执行这段传进来的逻辑。

四、 核心方法签名设计 (伪代码解析)

以“防缓存穿透的通用查询方法”为例,我们来看看它华丽的方法签名:

Java

/**
 * 处理缓存穿透的通用查询方法
 *
 * @param keyPrefix  Redis 的 key 前缀 (例如 "cache:shop:")
 * @param id         查询的 id (例如 1L)
 * @param type       返回值的 Class 类型 (为了将 JSON 反序列化为对象,例如 Shop.class)
 * @param dbFallback 查数据库的后备方案 (一个函数,例如 id -> getById(id))
 * @param time       过期时间
 * @param unit       过期时间单位
 * @return 泛型对象 R
 */
public <R, ID> R queryWithPassThrough(
        String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
    
    // 1. 拼接 Key
    // 2. 查 Redis
    // 3. 判断是否命中(如果命中是真的数据,直接返回;如果是空字符串 "",说明是穿透防御,返回 null)
    // 4. 未命中,调用传入的函数去查数据库:R r = dbFallback.apply(id);
    // 5. 数据库查不到,将空字符串写入 Redis 防穿透,返回 null
    // 6. 数据库查到了,序列化为 JSON 写入 Redis,设置 TTL
    // 7. 返回结果
}