【问题标题】:How to define multiple keys for @CachePut?如何为@CachePut 定义多个键?
【发布时间】:2023-03-24 22:43:01
【问题描述】:

如何在对象上使用@CachePut,并通过它的多个属性更新缓存?

示例: 每次调用 'findOneByFirstnameAndLastname()` 方法时,都会将缓存的人员添加到 PERSONS 缓存中。

如果Person 对象被持久化,我还希望缓存更新人。但是我如何告诉@CachePut 使用firstname+lastname 作为PERSONS 缓存的键?现在save() 方法确实更新缓存...

public interface PersonRepository extends CrudRepository<Person, Long> {
    //assume there is only one valid person
    @Cachable("PERSONS")
    Person findOneByFirstnameAndLastname(String firstname, String lastname);

    //TODO how to update cache by entity.firstname + entity.lastname
    @CachePut("PERSONS")
    @Override
    Person save(Person entity);
}

【问题讨论】:

  • 看起来你正试图像使用数据库一样使用缓存,不要那样做 :)
  • 我想更新数据库,并将最频繁的对象保存在PERSONS 缓存中(由进一步配置的 CacheManager 支持,但这对于问题并不重要)。为什么我不应该这样做,以减少延迟并在特定情况下限制查询流量......

标签: java spring caching spring-cache


【解决方案1】:

最后我成功地在lookip期间使用了对参数的引用(#p1, #2)并在persist期间引用了#result.*

@Cacheable(cacheNames = "PERSONS", key = "#p1 + #p2")
Person findByFirstnameAndLastName(String firstname, String lastname);

@CachePut(cacheNames = "PERSONS", key = "#result.firstName + #result.lastName")
Person save(Person person);

但我不知道为什么我不能使用#firstname + #lastname#person.firstname + #person.lastname,但是Spring 经常抱怨当时有null 参数。也许 Spring 在这个阶段无法解析参数名称,不管怎样。

【讨论】:

  • 我认为参数编号从0开始,如p0,p1
【解决方案2】:

我认为您选择的缓存键有点幼稚...名字 + 姓氏?

显然,更改一个人的名字并不少见,尤其是姓氏,例如一个人结婚时。她(有时是他的)姓氏通常会改变。

其次,我认为这(“假设只有一个有效的人”)在大多数情况下是一个非常薄弱的​​假设。

显然,您正在尝试在应用程序中缓存一个相当典型且常见的数据访问操作,例如“通过姓名查找某人”,该操作会调用搜索操作(查询)在您的 SD Repository 中定义,这可能是由一些 CSR 触发的,当有人打电话寻求支持时。但是,这是在错误的抽象级别上对缓存的不当使用。

如果你的缓存键发生变化,你认为会发生什么?

您基本上有内存泄漏,因为该条目基本上变得无法访问。 即使条目最终过期或被驱逐,如果过期或驱逐没有及时执行(当然,受内存限制和负载,以及您的缓存驱逐/过期策略)很容易耗尽内存.

对于大多数缓存来说,根据键的“散列”来缓存条目是很常见的。这在一些缓存实现(技术上是“数据网格”)中非常有用,其中数据在集群中的许多数据节点之间进行分区和平衡。不过,您可能知道,缓存与java.util.HashMap 没有什么不同。事实上,它们中的许多都实现了java.util.Mapjava.util.concurrent.ConcurrentMap 接口。

这通常是为什么在缓存中使用代理键而不是自然键(例如人名 + DOB 或 SSN 等)更可取的原因。代理键不太可能更改,并且仅用于内部,参考目的,特别是在基础数据可能并且可能会改变的情况下,我会在你的 UC 中说这很有可能。此外,鉴于在大多数缓存中使用“散列”,这就是为什么使用标量值(例如 Long)作为键的原因。

使用自然键只应非常小心,并且键的equalshashCode 方法已正确实现。

不过,如果您一心想要使用 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);

注意:或者,@CachePutkey”也可以定义为“#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)来减少传输的数据量并不少见。

坦率地说,优化查询并应用适当的索引比尝试将搜索结果存储在内存中更好。

希望这会有所帮助!

【讨论】:

  • 感谢您的详细想法。我可能要补充的是,“人”和“名字,姓氏”只是一个例子。我的业务用例要复杂得多,也非常具体,所以我做了一个每个人都能理解的简单例子。此外,我的问题与目标对象无关......
  • 我尝试了key = "#firstname + #lastname" 的方法,但spring 告诉我:org.springframework.expression.spel.SpelEvaluationException: EL1030E: The operator 'ADD' is not supported between objects of type 'null' and 'null'。当然我这里不是想把空值作为参数传递,但是为什么Spring会认为它们是空的呢?
  • 可能是因为你没有在调试模式下编译,所以不会在字节码中包含参数名称。但是,您也可以使用#p&lt;Number&gt; SpEL 变量引用,如下所示。
猜你喜欢
  • 2016-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多