如何为@CachePut 定义多个键?

How to define multiple keys for @CachePut?

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

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

如果持久化了Person对象,我也想让缓存更新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);
}

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

很明显,一个人的名字,尤其是姓氏,改名是很常见的,比如一个人结婚的时候。她(有时是他)的姓氏通常会改变。

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

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

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

您实际上存在内存泄漏,因为该条目基本上无法访问。 即使条目最终过期或被逐出,如果过期或逐出没有及时执行(当然,受内存限制和负载以及缓存 eviction/expiration 策略的影响)很容易 运行 内存不足。

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

这通常就是为什么在缓存中使用代理键而不是自然键(如人名 + DOB 或 SSN 等)更可取的原因。代理键不太可能永远改变并且仅用于内部参考目的,尤其是在底层数据可能并且可能发生变化的情况下,我会说在您的 UC 中很有可能。此外,考虑到在大多数缓存中使用 "hashing",这就是标量值(例如 Long)用作键的原因。

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

不过,如果您对使用 Person 的“名字 + 姓氏”一筹莫展,您可以使用 [=29] 定义缓存键=], 像这样...

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

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

NOTE: alternatively, the @CachePut "key" could also be defined as "#result.firstname + #result.lastname".

当然,如果您的 Person class 有一个 getName() 方法(或 name "property"),您可以像这样简化它。 ..

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

就我个人而言,我推荐一种稍微不同的缓存策略,特别是如果您不确定数据是否会很快被使用,或者 "frequently"。许多缓存根据使用频率保留数据,或根据最近最少使用 (LRU) 逐出数据。

通常情况下,当数据发生变化时仅 "evict" 个条目很常见,并且仅根据需要刷新缓存,当下次访问该条目时,将其从底层数据存储 (SOR) 中提取并存储在缓存中,如此...

interface PersonRepository extends CrudRepository<Person, Long> {

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

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

此缓存策略建议您不应缓存 "search result(s)",在给定时间范围内,结果数量可能非常广泛,尤其是在高并发上下文中,而是缓存实际获得的记录"used"(即加载或访问)。

此外,如果您稍后将查询方法更改为 return 一个 "projection" 而不是整个 Person 对象(可能包含许多其他对象),您的应用程序可能会崩溃"references",虽然在某种程度上也取决于您的延迟加载策略。尽管如此,通过使用只需要满足信息呈现的投影(或 DTO)来减少传输的数据量并不少见。

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

希望对您有所帮助!

最后,我成功地在 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 无法在此阶段解析参数名称,为什么。