如何实现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
相关产品推荐
相关产品推荐

