如何访问 Spring 中的缓存值

How to access cache values in Spring

场景:我需要在另一种方法中访问作为一种方法的一部分创建的缓存的值。我该怎么做?

public class Invoice {
  private String invoiced;
  private BigDecimal amount;
  //Getters and Setters
}

方法 1:当客户想要从 UI

获取发票列表时调用
@Cacheable(value="invoices")
public List<Invoice> getAllInvoices(String customerId){
...
//Get all invoices from Database and return 
...
return invoices
}

方法 2:当客户点击 UI

上的下载时调用
public File downloadInvoice(String invoiceId) {
 //TODO: 
 //Validate if invoiceId is present in the cache. This is a validation step
 //How can I access the cache "invoices" here.
 ...
 //if InvoiceId is present in cache then download from db else throw Exception
 return file;
}

注意:我没有使用任何缓存库

当您将方法注释为 @Cacheable 时,对该方法的第一次调用将在缓存中存储该方法的结果。对该方法的所有后续调用都将 return 缓存的结果,除非缓存被逐出。

getAllInvoices 的正常调用将 return 缓存的结果(如果先前已缓存结果)。

如需进一步阅读,我会推荐这本优秀的 article

正如 Spring 的缓存抽象 上的 documentation (and here) 所解释的那样,您必须启用缓存(即使用 @EnableCaching 注释注释配置,或使用 <cache:annotation-driven> 元素和 XML 配置) 声明类型 CacheManager 的 bean 以插入您选择的缓存提供程序(例如 Redis).

当然,当您使用 Spring Boot 时,这两个问题都是“auto-configured”你,前提是你已经在你的 Spring Boot 应用程序的类路径。

有关 Spring Boot 支持的缓存提供程序的完整列表(即“自动配置”),请参阅here.

这个(CacheManager bean 的here) is Spring Boot's caching auto-configuration for Redis, when Redis is on the application's classpath. Notice the declaration,必须准确命名为“cacheManager”。此设置对所有缓存提供程序都是相同的。

现在您知道 CacheManager 类型的“cacheManager”bean 存在于 Spring ApplicationContext 中,您可以使用CacheManageraccess the individual caches, which are represented by the Cache (Adapter) interface.

然后你可以...

@Service
class InvoiceService {

  private CacheManager cacheManager;

  public InvoiceService(CacheManager cacheManager) {
    this.cacheManager = cacheManager;
  }


  public File downloadInvoice(String invoiceId) {

    // Note: name of cache here (i.e. "invoices") must match the name
    // in the `@Cacheable` annotation on getAllInvoices(..).
    Cache invoices = this.cacheManager.getCache("invoices");

    // Now use the "invoices" `Cache` to find a reference to a `Invoice` with "invoiceId".
    // Read/load from database if `Invoice` is "cached"...
    // Do what must be done, etc ...

  }
}

但是,你有问题。

您的 @CacheablegetAllInvoices(..) 方法将“customerId”(键)缓存(即映射)到“发票”缓存中的 List<Invoice> 对象(值)。

您可能认为 @CacheablegetAllInvoices(..) 服务方法返回的 ListInvoices 由“invoiceId”单独缓存。但是,我向你保证,事实并非如此!

其实是...

customerId -> List<Invoice>

也就是说,由“发票”缓存中的“customerId”映射的 ListInvoice 个对象。

有一些方法可以改变默认行为(例如,如果需要,可以通过“invoiceId”在“invoices”缓存中缓存 List 中的单个 Invoice 对象,我在其他文章中对此进行了解释SO 关于缓存集合的帖子),但那是不是您的应用程序逻辑当前的设置和运行方式!

因此,您需要将“invoiceId”转换为“customerId”,以便通过“customerId”访问“invoices”Cache 中的 ListInvoice 个对象,然后迭代 List 以找到(可能的)由“invoiceId”缓存的 Invoice

或者,您可以更改缓存逻辑(推荐)。

终于...

没有缓存提供程序在后台是相同的。可能有一种方法可以独立访问各个缓存,具体取决于提供者但是,一般来说,您应该记住 Spring 的 Cache 表示在 Spring 的 缓存注释(例如 @Cacheable)或等效的 JSR-107、JCache API 注释 equivalents,不是 Spring 容器中的实际“豆”(不同于 CacheManager)。

在 Redis 中,缓存是 Redis HASHES(我相信)。

在GemFire/Geode(我最熟悉)中,缓存(即Cache)是一个GemFire/GeodeRegion 正好是 Spring bean 在 ApplicationContext.

此外,一些缓存提供程序还使用适当的模板(例如 GemfireTemplate,按区域)包装支持 Cache 的底层数据结构(例如 GemFire/Geode 区域)。

我不确定 (Sprig Data) Redis 是否为支持 Cache 的每个 HASH 创建了一个 RedisTemplate。然而,GemFire/Geode就是这种情况。所以你也可以做这样的事情......

@Service
class InvoiceService {

  @Resource(name = "invoices")
  private Region<String, Invoice> invoices;

  // ...

}

或者,或者(推荐)...

@Service
class InvoiceService {

  @Autowired
  @Qualifier("invoices")
  GemfireTemplate invoicesTemplate;

  // ... 

}

同样,这因缓存提供商而异,并且非常特定于提供商。

Cache 适配器接口是引用后备缓存实现的通用方式,如果您希望在环境之间切换缓存提供程序或出于其他原因,它很有用。

同样,要访问单个缓存(例如“发票”),您需要注入 CacheManager,因为并非所有缓存提供程序都为单个 Cache 个实例创建 bean。

请记住,我认为您将不得不稍微更改缓存设计。

希望这对您有所帮助。