Spring 在方法中更新读取值时缓存更新
Spring cache updates when read value updated in method
我正面临 spring 缓存问题,如下所述。检查是否有人遇到类似问题或任何可用的解决方法。我正在为简单用例使用通用缓存。
Spring 下面的缓存代码从数据库读取并缓存:
@Cacheable("all-items")
public List<ItemData> getAllItems() {
//DB fetch here
}
另一种方法是从缓存中读取:
public List<ItemData> getFilterData(String filter) {
List<ItemData> items = templateItemRepo.getAllItems();
// more code here to apply filter
staticItems.forEach(x -> {
items.removeIf(i -> i.getKey().equals(x)); //after this line, cache gets updated instead of maintaining its original data
});
}
在方法中本地修改从缓存中读取的项目后,令人惊讶的是缓存也得到了更新。
您希望在这种情况下发生什么?
您是否期望 List
个 ItemData
个对象(在本例中为缓存数据)不会被修改?
如果没有你的确切配置(和代码),肯定地说是不准确的,但根据你的描述,很容易推测这里发生了什么。
首先,您说您正在使用“通用缓存”。好吧,在 Spring 中,当您没有显式声明和配置特定的缓存提供程序时,例如 Redis、Hazelcast、Apache Geode (GemFire) 或其他方式(请参阅 here or here, and specifically, here), then what Spring gives you is a ConcurrentMap
cache implementation, by default (a ConcurrentHashMap
to be precise; see here),其中存储缓存值in-memory,在与您的 Spring [Boot] 应用程序相同的 JVM 进程中。
其次,假设 templateItemRepo
指的是包含您的 @Cacheable
getAllItems()
方法的存储库类型,那么第一次 getAllItems()
是从 Spring 容器,比如你的类型包含 getFilterData(:String)
方法,@Cacheable
getAllItems()
Repository 方法将被执行并且 returned List
of ItemData
对象将被缓存在一个简单的键下。
NOTE: A simple key is used in this case since the @Cacheable
getAllItems()
Repository method has no parameters. See documentation for more details.
此后,每次后续调用 @Cacheable
getAllItems()
Repository 方法将始终 return 相同 ,缓存 List
给定底层 Cache
实现的 ItemData
个对象是 ConcurrentMap
.
例如,您的“all-items”Cache
包含以下单个缓存条目 (key/value):
<simple-key> : List<ItemData>
对 @Cacheable
getAllItems()
存储库方法的所有后续调用始终使用相同的简单键,并且始终 return 对 List
的完全相同的对象引用=11=]存储在“all-items”Cache
.
使用 Redis、Hazelcast 或 Apache Geode 等缓存提供程序可能会导致不同的行为,因为这些数据存储以不同方式处理数据。
例如,Apache Geode 能够配置 copy-on-read 语义,意思是当值(例如 List
of ItemData
)从“all-items”Cache
中读取 (cache.get(key)
),您得到的是 List
及其所有 [=] 的真实(深度)“副本” 11=] 个对象,即使“all-items”Cache
是本地的,in-memory 与您的 Spring [Boot] 应用程序相同的 JVM 进程。
如果您的缓存提供程序用于 client/server 拓扑,那么 List
及其内容(ItemData
对象)将有必要在客户端和服务器之间序列化,并且再一次,您将得到的是 List
的“副本”(假设 ItemData
类型在某种程度上是可序列化的)。
每个缓存提供商将在这方面提供不同的功能,而其他可能显着影响数据的因素是逐出和过期策略。
如果您想在简单的“通用”缓存安排中实现与缓存提供程序类似的行为,那么您需要手动制作 List
的 [深] 副本。
当然,您可以从 Spring 的缓存抽象中创建 Adapter (Wrapper) or Decorator implementations of the primary Cache
(Javadoc) and CacheManager
(Javadoc) 接口,它可以为您处理此复制。 Wrapper 类型将包装来自实际缓存提供程序的实际 Cache
和 CacheManager
接口,即使使用 ConcurrentMap
支持的简单 Cache
实现也是如此。
但是,在使用复杂的缓存提供程序时,最好让提供程序处理复制、序列化等操作。不过,对于测试简单的情况,上述方法可能很有用。
我正面临 spring 缓存问题,如下所述。检查是否有人遇到类似问题或任何可用的解决方法。我正在为简单用例使用通用缓存。
Spring 下面的缓存代码从数据库读取并缓存:
@Cacheable("all-items")
public List<ItemData> getAllItems() {
//DB fetch here
}
另一种方法是从缓存中读取:
public List<ItemData> getFilterData(String filter) {
List<ItemData> items = templateItemRepo.getAllItems();
// more code here to apply filter
staticItems.forEach(x -> {
items.removeIf(i -> i.getKey().equals(x)); //after this line, cache gets updated instead of maintaining its original data
});
}
在方法中本地修改从缓存中读取的项目后,令人惊讶的是缓存也得到了更新。
您希望在这种情况下发生什么?
您是否期望 List
个 ItemData
个对象(在本例中为缓存数据)不会被修改?
如果没有你的确切配置(和代码),肯定地说是不准确的,但根据你的描述,很容易推测这里发生了什么。
首先,您说您正在使用“通用缓存”。好吧,在 Spring 中,当您没有显式声明和配置特定的缓存提供程序时,例如 Redis、Hazelcast、Apache Geode (GemFire) 或其他方式(请参阅 here or here, and specifically, here), then what Spring gives you is a ConcurrentMap
cache implementation, by default (a ConcurrentHashMap
to be precise; see here),其中存储缓存值in-memory,在与您的 Spring [Boot] 应用程序相同的 JVM 进程中。
其次,假设 templateItemRepo
指的是包含您的 @Cacheable
getAllItems()
方法的存储库类型,那么第一次 getAllItems()
是从 Spring 容器,比如你的类型包含 getFilterData(:String)
方法,@Cacheable
getAllItems()
Repository 方法将被执行并且 returned List
of ItemData
对象将被缓存在一个简单的键下。
NOTE: A simple key is used in this case since the
@Cacheable
getAllItems()
Repository method has no parameters. See documentation for more details.
此后,每次后续调用 @Cacheable
getAllItems()
Repository 方法将始终 return 相同 ,缓存 List
给定底层 Cache
实现的 ItemData
个对象是 ConcurrentMap
.
例如,您的“all-items”Cache
包含以下单个缓存条目 (key/value):
<simple-key> : List<ItemData>
对 @Cacheable
getAllItems()
存储库方法的所有后续调用始终使用相同的简单键,并且始终 return 对 List
的完全相同的对象引用=11=]存储在“all-items”Cache
.
使用 Redis、Hazelcast 或 Apache Geode 等缓存提供程序可能会导致不同的行为,因为这些数据存储以不同方式处理数据。
例如,Apache Geode 能够配置 copy-on-read 语义,意思是当值(例如 List
of ItemData
)从“all-items”Cache
中读取 (cache.get(key)
),您得到的是 List
及其所有 [=] 的真实(深度)“副本” 11=] 个对象,即使“all-items”Cache
是本地的,in-memory 与您的 Spring [Boot] 应用程序相同的 JVM 进程。
如果您的缓存提供程序用于 client/server 拓扑,那么 List
及其内容(ItemData
对象)将有必要在客户端和服务器之间序列化,并且再一次,您将得到的是 List
的“副本”(假设 ItemData
类型在某种程度上是可序列化的)。
每个缓存提供商将在这方面提供不同的功能,而其他可能显着影响数据的因素是逐出和过期策略。
如果您想在简单的“通用”缓存安排中实现与缓存提供程序类似的行为,那么您需要手动制作 List
的 [深] 副本。
当然,您可以从 Spring 的缓存抽象中创建 Adapter (Wrapper) or Decorator implementations of the primary Cache
(Javadoc) and CacheManager
(Javadoc) 接口,它可以为您处理此复制。 Wrapper 类型将包装来自实际缓存提供程序的实际 Cache
和 CacheManager
接口,即使使用 ConcurrentMap
支持的简单 Cache
实现也是如此。
但是,在使用复杂的缓存提供程序时,最好让提供程序处理复制、序列化等操作。不过,对于测试简单的情况,上述方法可能很有用。