如何为@CachePut定义多个键?

时间:2017-10-26 15:07:34

标签: java spring caching spring-cache

如何在对象上使用@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);
}

2 个答案:

答案 0 :(得分:0)

我认为你选择缓存密钥有点天真......名字+姓氏?

显然,一个人的名字改变,特别是姓氏,例如一个人结婚时,这种情况并不少见。她(通常是他的)姓氏通常会改变。

其次,我认为这个(&#34; 假设只有一个有效的人&#34;)在大多数情况下是一个相当弱的假设。

显然,您正在尝试在应用程序中缓存一个相当典型且常见的数据访问操作,例如&#34; 按姓名&#34;查找一个人,这会调用SD 存储库中定义的搜索操作(查询),当某人呼叫支持时,可能由某些CSR触发。但是,这是在错误的抽象级别上不适当地使用缓存。

如果缓存密钥发生变化,您认为会发生什么?

由于条目基本上无法访问,因此您实际上有内存泄漏。 即使条目最终到期或被驱逐,如果未及时执行到期或驱逐(受内存限制和加载,当然还有缓存驱逐/过期策略),很容易耗尽内存

对于大多数缓存来说,基于&#34; hash&#34;来缓存条目是很常见的。关键。这在一些缓存实现(技术上,&#34; 数据网格&#34;)中非常有用,其中数据在群集中的许多数据节点之间进行分区和平衡。虽然,您可能知道,缓存与java.util.HashMap没有什么不同。实际上,他们中的许多人确实实现了java.util.Mapjava.util.concurrent.ConcurrentMap接口。

这通常是为什么最好在缓存中使用代理键而不是自然键,例如人名+ DOB或SSN等。代理键不可能永远改变,只是用于内部的,参考的目的,特别是在基础数据可能和可能改变的可能性中,我在你的UC中很可能会说。另外,考虑到使用&#34;哈希&#34;在大多数缓存中,这就是标量值(例如Long)用作键的原因。

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

尽管如此,如果您已经开始使用&#34; 第一个+姓氏&#34;在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&#34; key&#34;也可以定义为&#34; #result.firstname + #result.lastname&#34;。

当然,如果您的Person课程采用getName()方法(或name&#34;属性&#34;),您可以简化此操作,就像这样... < / p>

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了解更多详情。

就个人而言,我建议采用略有不同的缓存策略,特别是如果您不是很确定数据会在相对较快时使用,或者经常&#34;。许多缓存根据使用频率保存数据,或基于最近最少使用(LRU)逐出。

通常,只是&#34;逐出&#34;数据更改时的条目,只在需要时刷新缓存,下次访问该条目时,将其从基础数据存储(SOR)中拉出并存储在缓存中,如此...

interface PersonRepository extends CrudRepository<Person, Long> {

  @Cacheable("PERSONS")
  Person findById(Long id);

  @CacheEvict(cacheNames = "PERSONS", key = "#result.id")
  Person save(Person person);
}

此缓存策略建议您不应缓存搜索结果&#34;,其中结果的数量在给定时间范围内可能相当广泛,尤其是在高度并发的上下文中,而是缓存实际获得的记录&#34;使用&#34; (即加载或访问)。

此外,如果您以后更改查询方法以返回&#34;投影&#34;您的应用程序可能会崩溃。而不是整个Person对象,它可能包含许多其他&#34;引用&#34;,但在某种程度上也取决于您的延迟加载策略。尽管如此,通过使用仅满足信息呈现所必需的投影(或DTO)来减少传输的数据量并不罕见。

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

希望这有帮助!

答案 1 :(得分:0)

最后,我在查看过程中成功使用了对参数(#p1, #2)的引用,并在持久化过程中引用了#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在这个阶段无法解析参数名称,无论如何。