我认为您选择的缓存键有点幼稚...名字 + 姓氏?
显然,更改一个人的名字并不少见,尤其是姓氏,例如一个人结婚时。她(有时是他的)姓氏通常会改变。
其次,我认为这(“假设只有一个有效的人”)在大多数情况下是一个非常薄弱的假设。
显然,您正在尝试在应用程序中缓存一个相当典型且常见的数据访问操作,例如“通过姓名查找某人”,该操作会调用搜索操作(查询)在您的 SD Repository 中定义,这可能是由一些 CSR 触发的,当有人打电话寻求支持时。但是,这是在错误的抽象级别上对缓存的不当使用。
如果你的缓存键发生变化,你认为会发生什么?
您基本上有内存泄漏,因为该条目基本上变得无法访问。
即使条目最终过期或被驱逐,如果过期或驱逐没有及时执行(当然,受内存限制和负载,以及您的缓存驱逐/过期策略)很容易耗尽内存.
对于大多数缓存来说,根据键的“散列”来缓存条目是很常见的。这在一些缓存实现(技术上是“数据网格”)中非常有用,其中数据在集群中的许多数据节点之间进行分区和平衡。不过,您可能知道,缓存与java.util.HashMap 没有什么不同。事实上,它们中的许多都实现了java.util.Map 或java.util.concurrent.ConcurrentMap 接口。
这通常是为什么在缓存中使用代理键而不是自然键(例如人名 + DOB 或 SSN 等)更可取的原因。代理键不太可能更改,并且仅用于内部,参考目的,特别是在基础数据可能并且可能会改变的情况下,我会在你的 UC 中说这很有可能。此外,鉴于在大多数缓存中使用“散列”,这就是为什么使用标量值(例如 Long)作为键的原因。
使用自然键只应非常小心,并且键的equals 和hashCode 方法已正确实现。
不过,如果您一心想要使用 Person 的“名字 + 姓氏”,您可以使用 SpEL expression 定义缓存键,就像这样...
@Cacheable(cacheNames = "PERSONS", key = "#firstname + #lastname")
Person findByFirstnameAndLastName(String firstname, String lastname);
@CachePut(cacheNames = "PERSONS", key = "#person.firstName + #person.lastName")
Person save(Person person);
注意:或者,@CachePut“key”也可以定义为“#result.firstname + #result.lastname”。
当然,如果你的Person 类有一个getName() 方法(或name“属性”),你可以简化这个,像这样......
class Person {
...
String getName() {
return String.format("%1$s %2$s", getFirstName(), getLastName());
}
}
那么……
@Cacheable(cacheNames = "PERSONS", key = "#firstname + ' ' + #lastname")
Person findByFirstnameAndLastName(String firstname, String lastname);
@CachePut(cacheNames = "PERSONS", key = "#person.name")
Person save(Person person);
有关详细信息,请参阅SpEL's mathematical operators,尤其是涉及字符串连接的内容。
有关详细信息,另请参阅Section on Cache Keys。
就我个人而言,我建议使用稍微不同的缓存策略,尤其是在您不确定数据会相对较快或“频繁”使用的情况下。许多缓存根据使用频率保留数据,或根据最近最少使用 (LRU) 驱逐数据。
通常情况下,通常只是在数据更改时“驱逐”条目并仅根据需要刷新缓存,当下次访问该条目时,将其从底层数据存储 (SOR) 中拉出并将其存储在缓存中,就这样……
interface PersonRepository extends CrudRepository<Person, Long> {
@Cacheable("PERSONS")
Person findById(Long id);
@CacheEvict(cacheNames = "PERSONS", key = "#result.id")
Person save(Person person);
}
这种缓存策略建议您不应该缓存“搜索结果”,因为在给定的时间范围内结果的数量可能非常广泛,尤其是在高度并发的上下文中,而是缓存实际获得的记录“使用”(即加载或访问)。
此外,如果您稍后更改查询方法以返回“投影”而不是返回整个 Person 对象(可能包含许多其他“引用”),您的应用程序可能会崩溃,但在某种程度上取决于您的延迟加载策略也是如此。尽管如此,使用仅满足信息呈现所必需的投影(或 DTO)来减少传输的数据量并不少见。
坦率地说,优化查询并应用适当的索引比尝试将搜索结果存储在内存中更好。
希望这会有所帮助!