You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Guava LoadingCache的getAll方法避免频繁访问数据库

解决方案:基于Guava缓存实现返回全量数据的getAll()方法

要实现无需每次访问数据库的getAll(),核心思路是确保首次调用时将全量数据加载到缓存,后续直接从缓存读取,同时利用Guava缓存的能力提升效率。以下是两种可行方案:

方案一:利用Guava的批量加载能力(推荐)

如果你的DAO能提供所有数据的键集合(比如getAllContractKeys()),可以借助Guava LoadingCache的getAll(keys)方法,自动加载未缓存的键对应的数据:

修改后的getAll()实现

public List<ContractCategoryModel> getAll() {
    try {
        // 从DAO获取所有数据的键
        Set<String> allKeys = dao.getAllContractKeys();
        // 批量加载所有键到缓存(未缓存的键会自动调用CacheLoader的load方法从DB获取)
        Map<String, ContractCategoryModel> allModels = contractCategoryCache.getAll(allKeys);
        
        return allModels.values().stream()
                .sorted(Comparator.comparing(ContractCategoryModel::getCategory))
                .collect(Collectors.toList());
    } catch (ExecutionException e) {
        throw new RuntimeException("加载全量合同分类失败", e);
    }
}

测试代码调整

调用cache.getAll()时,Guava会自动加载所有键对应的数据,因此测试断言会通过:

public void cache_get_all_success() {
    when(mockDAO.getContractModel("AAA")).thenReturn(CATEGORY_A);
    when(mockDAO.getContractModel("BBB")).thenReturn(CATEGORY_B);
    // 模拟返回所有键
    when(mockDAO.getAllContractKeys()).thenReturn(Set.of("AAA", "BBB"));

    ContractCategoryCache cache = new ContractCategoryCache(mockDAO);
    Optional<ContractCategoryModel> modelA = cache.get("AAA");

    assertEquals(2, cache.getAll().size());
}

方案二:懒加载全量数据到缓存(适用于DAO支持全量查询)

如果DAO直接提供全量查询方法(比如getAllContractModels()),可以用双重检查锁实现首次调用时一次性加载所有数据到缓存:

修改后的getAll()实现

// 新增volatile标记,确保多线程下可见性
private volatile boolean isAllDataLoaded = false;

public List<ContractCategoryModel> getAll() {
    // 双重检查锁,避免重复加载
    if (!isAllDataLoaded) {
        synchronized (this) {
            if (!isAllDataLoaded) {
                // 从DB获取全量数据
                List<ContractCategoryModel> allFromDB = dao.getAllContractModels();
                // 批量放入缓存
                allFromDB.forEach(model -> contractCategoryCache.put(model.getCategory(), model));
                isAllDataLoaded = true;
            }
        }
    }

    // 从缓存读取并排序返回
    return contractCategoryCache.asMap().values().stream()
            .sorted(Comparator.comparing(ContractCategoryModel::getCategory))
            .collect(Collectors.toList());
}

补充说明

  • 数据更新处理:如果数据存在变更需求,可以给Guava缓存设置过期时间(expireAfterWrite),或提供手动刷新缓存的方法(比如refreshAll()),在DB数据变更时调用。
  • 规避你提到的方案弊端:
    • 记录数对比:无法应对DB数据新增/删除的实时变化,还会额外增加一次DB查询。
    • 简单布尔标记:未处理并发场景,双重检查锁方案解决了这个问题。

内容的提问来源于stack exchange,提问作者user19778365

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 20:51:19