【发布时间】:2022-10-05 04:58:46
【问题描述】:
我需要多次更新缓存中某个对象的一个字段(该对象的最后查询时间)而不在数据库中更新它,但是在spring从缓存中删除对象之前,我需要在数据库中更新它。有没有办法配置缓存管理器,以便在从缓存中删除对象之前自动更新数据库中已删除的缓存对象?
我正在使用标准注释,例如 @Cacheable @CachePut @CacheEvict
【问题讨论】:
标签: spring-boot caching spring-cache
我需要多次更新缓存中某个对象的一个字段(该对象的最后查询时间)而不在数据库中更新它,但是在spring从缓存中删除对象之前,我需要在数据库中更新它。有没有办法配置缓存管理器,以便在从缓存中删除对象之前自动更新数据库中已删除的缓存对象?
我正在使用标准注释,例如 @Cacheable @CachePut @CacheEvict
【问题讨论】:
标签: spring-boot caching spring-cache
这是一个加载的问题,因为 1)答案部分取决于配置的缓存提供程序由您使用弹簧 [引导]应用程序(例如 Redis)和 2)Spring的缓存抽象就是这样,一个“抽象”(而不是“缓存实现”),并且在技术上留下了复杂的问题,比如 Eviction and Expiration policies 给底层缓存提供程序, 里程可以在不同的缓存提供程序在这种情况下(即 Redis、Hazelcast、Apache Geode 等)。
话虽如此,正如你已经知道的那样,春天除了programmatical support(或alternatively) 直接通过Cache 接口进行驱逐。
快速而肮脏的答案是,没有提供直接支持春天开箱即用,因为它 1) 是一种抽象,而且 2) 有很多方法可以解决这个问题。
但是,您确实有几个选择:
@CacheEvict 带注释的应用程序服务组件(bean)方法中实现解决方案,例如:
@Service
class MyCachingBasedApplicationService {
// NOTE: Use constructor-based injection instead
@Autowired
private CacheManager cacheManager;
@Autowired
private UserRepository userRepository;
@CacheEvict(cacheNames = "Users")
public void evictUser(String userId) {
Cache userCache = this.cacheManager.getCache("Users");
User target = userCache.get(userId, User.class);
this.userRepository.save(target);
}
记下@CacheEviction 注释上的beforeInvocation 属性,并确定在这种情况下您的应用程序的最佳配置。
当然,这个解决方案需要直接调用上面显示的应用服务组件的evictUser(..) 方法,并且没有考虑到“自然”的驱逐/过期策略,这些策略也可能是固有的缓存提供程序,如配置。
此外,会有一个时间窗口 (IIUC) 支持数据库和您的缓存不同步,如果一致性是一个问题,那么这肯定是一个问题,因为您没有更新缓存对象,就像User,直到它(显式)从缓存中驱逐,当然,假设在这种情况下不止一个lastAccessedTime字段/属性也在更新,或者lastAccessedTime字段/属性对于从数据库(直接)、可能通过应用程序的其他部分或从通过数据库共享相同数据的其他应用程序看到的应用程序的正确行为。
User 的lastAccessedTime 字段/属性,则一旦变异应用程序服务操作完成,缓存对象就可以更新通过调用@CachePut注解的应用服务组件方法,实时获取对应的数据库记录。这样@CacheEvict 注释的应用程序服务组件方法就不需要像上面#1 中建议的那样关注更新数据库。可以说,除了缓存的一般用途,即快速访问和最小化应用程序和外部应用程序资源(数据库、消息队列、其他微服务等)之间的延迟之外,缓存还可以用于尽可能保持频繁访问数据的一致视图。
但是,每次访问/更新缓存对象并“放”回缓存时,这种方法可能会导致更多的数据库流量。
例如,(免责声明:我的专业领域是 Apache Geode)在使用 Apache Geode as a caching provider in Spring's Cache Abstraction 时,Apache Geode Region 是 Spring 的 CacheManager @987654330 返回的“命名”Cache instance 的后备存储@ 由...提供Apache Geode 的 Spring 数据(可持续发展目标)。
Apache Geode Region 允许 CacheListener 的 registration。 CacheListener 接口允许在缓存层或由缓存提供程序直接地。
这里的好处是这种方法通常更有效,并且可以更优雅地扩展更长的时间来处理其他问题,比如一致性,尤其是在分布式缓存解决方案中。
当然,缺点是解决方案/实现很明显缓存提供程序具体的,如果你改变了,就需要改变缓存提供程序(例如,从 Geode 到 Redis,从 Redis 到 Hazelcast,等等)。
我在回答中没有提到许多其他问题。
您可能还可以想到其他解决方案,例如使用自定义 Spring AOP 方面(Spring 的缓存抽象毕竟是基于 AOP 的,它本质上归结为适当地对方面进行排序),对 Spring 的缓存抽象实现更复杂的扩展(以及具体来说,主要的Cache 和CacheManager 接口是抽象的核心)等。
无论如何,我希望这能给你一些想法。
【讨论】: