@CacheEvict 的 Gemfire EntryNotFoundException

Gemfire EntryNotFoundException for @CacheEvict

简而言之,当在方法上调用@CacheEvict 时,如果未找到条目的键,Gemfire 将抛出 EntryNotFoundException。

现在详细说,

我有一个class

class Person {

 String mobile;
 int dept;
 String name;

}

我有两个缓存区域,定义为 personRegion 和 personByDeptRegion,服务如下

@Service
class PersonServiceImpl {

   @Cacheable(value = "personRegion")
   public Person findByMobile(String mobile) {

      return personRepository.findByMobile(mobile);

   }


   @Cacheable(value = "personByDeptRegion")
   public List<Person> findByDept(int deptCode) {

      return personRepository.findByDept(deptCode);

   }


   @Caching(
      evict = { @CacheEvict(value = "personByDeptRegion", key="#p0.dept"},
      put = { @CachePut(value = "personRegion",key = "#p0.mobile")}

   )
   public Person updatePerson(Person p1) {

      return personRepository.save(p1);

   }

}

当调用 updatePerson 并且 personByDeptRegion 中没有条目时,这将引发异常,即键 1(或任何部门代码)的 EntryNotFoundException。很有可能在调用@Cacheable 方法之前调用此方法并希望避免此异常。 当给定区域不存在密钥时,有什么方法可以将 Gemfire 的行为调整为优雅 return 吗? 或者,我也很想知道是否有更好的使用 Gemfire 作为缓存实现上述场景。

Spring 数据 Gemfire:1.7.4

Gemfire 版本:v8.2.1

注意:以上代码仅供参考,我在实际项目中有多个服务存在相同问题。

首先,我赞扬您在应用程序 @Service 组件上使用 Spring 的 缓存注释。开发人员经常在他们的存储库中启用缓存,我认为这是不好的形式,特别是如果在存储库交互之前或之后涉及复杂的业务规则(或什至额外的 IO;例如从服务组件调用 Web 服务) ,特别是在不应影响(或确定)缓存行为的情况下。

我还认为您的缓存 UC(更新一个缓存 (personRegion),同时在数据存储更新时使另一个缓存 (personByDeptRegion) 无效)通过 CachePutCacheEvict 对我来说似乎是合理的。不过,我要指出 @Caching 注释的看似预期用途是组合 相同 类型的多个缓存注释(例如多个 @CacheEvict 或多个 @CachePut) 如核心 Spring 框架 Reference Guide 中所述。不过,没有什么能阻止您的预期用途。

我创建了一个类似的测试 class here, modeled after your example above, to verify the problem. Indeed the jonDoeUpdateSuccessful test case fails (with the GemFire EntryNotFoundException, shown below) since no people in Department "R&D" were previously cached in the "DepartmentPeople" GemFire Region prior to the update, unlike the janeDoeUpdateSuccessful 测试用例,它导致缓存在更新之前被填充(即使条目没有值,这没有任何影响)。

com.gemstone.gemfire.cache.EntryNotFoundException: RESEARCH_DEVELOPMENT
    at com.gemstone.gemfire.internal.cache.AbstractRegionMap.destroy(AbstractRegionMap.java:1435)

NOTE: My test uses GemFire as both a "cache provider" and a System of Record (SOR).

真正的问题在于SDG对Region.destroy(key) in the GemfireCache.evict(key) implementation rather than, and perhaps more appropriately, Region.remove(key)的使用。

GemfireCache.evict(key) 从一开始就用 Region.destroy(key) 实现。但是,直到 GemFire v5.0 才引入 Region.remove(key)。不过,除了 Region.destroy(key) 抛出的 EntryNotFoundException 之外,我看不出 Region.destroy(key)Region.remove(key) 之间有什么明显区别。本质上,它们既销毁本地条目(键和值),又将操作分发到集群中的其他缓存(提供非 LOCAL Scope 使用)。

因此,我已提交 SGF-539 以将 SDG 更改为在 GemfireCache.evict(key) 中调用 Region.remove(key) 而不是 Region.destroy(key)

至于解决方法,基本上只有两件事可以做:

  1. 重构您的代码和对 @CacheEvict 注释的使用,and/or...
  2. 利用 @CacheEvict 上的 condition

不幸的是 condition 不能使用 class 类型指定,类似于 Spring Condition(除 SpEL 外),但此接口用于其他目的,@CacheEvictcondition 属性不接受 class 类型。

目前,我没有一个很好的例子来说明这可能是如何工作的,所以我继续 SGF-539

您可以关注此票以了解更多详细信息和进度。

对于给您带来的不便,我们深表歉意。

-约翰