外观
查询结果缓存
DataCacheUtil 把“读缓存,未命中则加载并写回”的流程封装为方法调用。对象和列表通过 Fastjson2 转为 JSON 字符串,保存在默认 storeTemplate.value() 中;接入 Starter 后可直接注入,不需要为每个对象编写 Codec。
缓存对象与列表
以下演示类放在应用扫描范围内。sourceLoads 模拟查询次数,不连接业务后端;实际应用将 supplier 替换为已有的查询方法。
java
package example.store;
import com.own.component.store.api.cache.DataCacheUtil;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Service
public class LookupCacheDemo {
private final DataCacheUtil cache;
private final AtomicInteger sourceLoads = new AtomicInteger();
public LookupCacheDemo(DataCacheUtil cache) {
this.cache = cache;
}
public String label(String code) {
return cache.get("label:v1:" + code,
() -> code + "-load-" + sourceLoads.incrementAndGet(),
String.class, Duration.ofMinutes(5));
}
public List<String> supportedCodes() {
return cache.list("supported-codes:v1",
() -> List.of("cn", "en"),
String.class, Duration.ofMinutes(5));
}
public void invalidate(String code) {
cache.delete("label:v1:" + code);
}
}在缓存启用、没有其他写入或淘汰的情况下,连续调用 label("cn") 两次会得到相同结果;执行 invalidate("cn") 后再调用,查询次数增加。对象版本使用 get(key, supplier, Result.class, ttl),列表版本的 Class<T> 是元素类型,不是 List.class。
键应包含影响结果的查询条件、租户或用户范围以及结构版本。工具不自动加入权限上下文,也不校验一个键是否被用于另一种对象结构;同一键混用对象、列表或其他 Value 写入会互相覆盖。
有效期选择
get 和 list 都提供以下重载,前面三个参数固定为 key、supplier、Class<T>:
| 第四个参数 | 行为 |
|---|---|
| 不传 | 写入后不设置过期时间 |
Duration | 正数表示写入后的有效时长;null、零或负数表示不设置过期时间 |
LocalDateTime | 转成 JVM 默认时区下的截止时间;null 不合法,时间已到则写入后删除 |
Consumer<ValueStore<String>> | 成功写入后执行自定义过期动作,调用方需针对当前 key 操作 |
这里 Duration 的非正值语义与低层 Store TTL不同。传 null 还会遇到 Java 重载歧义,应优先省略第四个参数表达无 TTL。
过期策略只在回源写入后执行。命中时不续期,也不采用本次调用新传入的 TTL 或截止时间。希望修改已有缓存期限时,应明确失效并重新加载,或直接管理该键的过期时间。
工具先 set 再执行过期动作,二者不原子。过期动作抛出运行时异常时,会按刚写入的 JSON 条件删除并继续抛出异常;进程中断仍可能留下未设期限的值。因此它适合可重建的查询缓存,不应用于必须严格到期的凭证生命周期控制。
并发回源与异常
读取未命中后,工具获取逻辑键为 data-cache:<缓存key> 的锁,在锁内再次读取,仅第二次仍未命中时调用 supplier。默认等待锁没有超时参数。不同缓存键有不同逻辑锁,本地锁分片碰撞仍可能令它们串行。
共享 Redis 缓存时还需要共享、范围匹配的锁管理器;只使用共享数据配合本地锁,不能阻止多个实例同时回源。自动装配与手动替换注意事项见后端与配置。
| 情况 | 处理 |
|---|---|
| supplier 返回 null 对象 | 返回 null,不缓存 |
| supplier 返回 null 或空列表 | 原样返回,不缓存,下次仍会回源 |
| JSON 损坏或解码为 null | 记录告警,按原内容条件删除,再按未命中处理 |
| supplier 抛异常 | 传播异常,不写入新缓存 |
| 序列化失败 | 抛出 StoreException |
| Store、锁或网络访问失败 | 不提供自动绕过缓存的回源兜底 |
解码成功的已有空列表仍算命中。“损坏恢复”也不能识别所有结构变化:合法 JSON 可能解析成功但丢失字段,应通过版本化键控制兼容性。缓存不保存空结果,因此这套流程不提供负缓存,也不保证 supplier 永久只执行一次。
失效与事务
数据更新后由应用安排 cache.delete(key);工具不会监听数据库变化。删除与并发加载没有统一锁定的更新协议,写后立即删除也不能自动保证强一致,应结合应用的一致性要求安排失效时机。
在 Spring 同步事务内调用时,默认锁策略可能把回源锁保留到事务完成,但缓存值仍在 supplier 返回后立即写入,并不等待提交。数据库回滚不会撤销缓存,其他请求的无锁缓存读取也可能提前看到值。涉及事务内修改的数据,应在提交后加载或失效,不能把事务感知锁当成缓存事务。
临时绕过缓存
yaml
own:
store:
enable-data-cache: false该配置作用于自动创建的 DataCacheUtil:Bean 仍存在,查询直接调用 supplier,既不读写缓存也不加回源锁,delete 也不访问 Store。它不会清理已经保存的键,重新启用后仍可能读到旧缓存;也不会禁用 StoreTemplate 的直接操作。该值在 Bean 创建时读取,组件未提供动态刷新机制。