跳转到正文

查询结果缓存

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 写入会互相覆盖。

有效期选择

getlist 都提供以下重载,前面三个参数固定为 keysupplierClass<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 创建时读取,组件未提供动态刷新机制。