有没有办法分别为saveAll或updateAll应用Spring缓存功能,使用Cacheable函数?

huangapple go评论86阅读模式
英文:

Is there a way to apply Spring caching functionality for saveAll Or updateAll with Cacheable function, respectively?

问题

@CachePut(cacheNames = "projectTeams", key = "#projectTeam.id")
public List<ProjectTeam> updateAll(List<ProjectTeam> projectTeam) {
   return projectTeamRepository.save(projectTeam);
}

@Cacheable(cacheNames = "projectTeams", key = "#id")
public List<ProjectTeam> findByIds(ListOfIds<Long> ids) {
    log.info("get project team from db");
    log.info("projectTeam id " + id);
    return projectTeamRepository.findByIds(ids);
}

我不知道应该在key中放什么内容。Spring Cache是否支持SaveAll

英文:
@CachePut(cacheNames = &quot;projectTeams&quot;, key = &quot;#projectTeam.id&quot;)
public List&lt;ProjectTeam&gt; updateAll(List&lt;ProjectTeam&gt; projectTeam) {
   return projectTeamRepository.save(projectTeam);
}


@Cacheable(cacheNames = &quot;projectTeams&quot;, key = &quot;#id&quot;)
public List&lt;ProjectTeam&gt; findByIds(ListOfIds&lt;Long&gt; ids) {
log.info(&quot;get project team from db&quot;);
log.info(&quot;projectTeam id &quot; + id);
return projectTeamRepository.findByIds(ids);
} 

I don't have any idea what i should put in key.
Does spring cache supports SaveAll.

答案1

得分: 1

Spring使用默认的键生成策略从启用缓存的(@Cacheable@CachePut注解的)服务或存储库方法的参数中派生缓存条目的键。

当然,您可以通过以下方式自定义任何启用缓存的bean方法使用的键生成:1) 实现KeyGenerator接口,2) 在缓存注解中声明KeyGenerator bean,如下所示:

class ListKeyGenerator implements KeyGenerator {

  public Object generate(Object target, Method method, Object... params) {

    List<?> list = params[0];

    // 使用列表生成缓存条目的(唯一)键
    Object generatedKey = ...;

    return generatedKey;
  }
}

注意: target是包含您的updateAll(..)findByIds(..)方法的Spring托管bean。Method引用是被调用的启用缓存的方法,例如updateAll(..)。而Object参数数组是缓存启用方法的参数(例如List)。

提示: 为了简单和清晰(推荐),您可能希望有两个独立的基于ListKeyGenerators,其中一个是ID的List(在finder/query方法中使用),另一个是ProjectTeam实例的List(在更新中使用)。

在配置中,您可以声明:

@Configuration
class ApplicationCacheConfiguration {

  @Bean
  KeyGenerator listKeyGenerator() {
    return new ListKeyGenerator();
  }
}

您可以使用以下方式应用自定义的KeyGenerator

@Service
class ProjectTeamService {

  @CachePut(cacheNames = "projectTeams", keyGenerator = "listKeyGenerator")
  public List<ProjectTeam> updateAll(List<ProjectTeam> projectTeams) {
    // 执行必要的预处理步骤
    return this.projectTeamRepository.saveAll(projectTeams);
  }
}

注意: 请参阅@Cacheable的相应Javadoc@CachePutJavadoc

然而,如果您只依赖于Spring的默认键生成策略,则不一定需要自定义KeyGenerator

关于您选择的方法,这将不起作用,因为它试图使用Spring SpEL表达式来访问ProjectTeam对象的List中的单个元素/项。对于ID的List也是如此。

您从List中使用哪个元素?您使用所有元素(这将很难使用SpEL进行汇总)?如果传递给启用缓存方法的List参数为空,或更糟糕的是null,会发生什么?

提示: 您应该查看Spring的SpEL功能以获取更多详细信息。

这些类型的问题更适合在自定义KeyGenerator实现中处理。

您还应该知道,Spring托管bean的启用缓存方法的缓存条目将使用“默认键生成”:

cache-enabled method          | cache key         | cache value
----------------------------------------------------------------------
findByIds(:List<Long>)        | List<Long>        | List<ProjectTeam>
updateAll(:List<ProjectTeam>) | List<ProjectTeam> | List<ProjectTeam>

如果您期望将List中的个别元素/项(例如Long ID或ProjectTeam实例)分开并单独缓存在不同的缓存条目中,那么您是错误的,因为这不是Spring的缓存抽象默认工作方式。

我提到这一点是因为您在注解中声明的缓存配置似乎暗示了这一点。

Spring获取启用缓存方法的参数(例如@Cacheable注解的方法)作为缓存条目的键。方法的返回值用作值。由于启用缓存方法接受一个List并返回一个List,所以缓存条目的键是List参数,而List返回值是缓存条目的值。

可以想象,对于请求的每个不同的ID列表或ProjectTeam实例的List,您的缓存内存空间会迅速填满!这是因为如果每个请求(启用缓存方法的调用)中的List只有1个元素不同,那么它将构成缓存中的新条目。根据应用程序处理的不同请求(调用)的数量和频率,这可能会导致严重问题(例如OOME)!

如果您需要单独请求的项目具有单独的缓存条目,请参阅早期的这篇SO帖子

祝你好运!

英文:

Spring uses a default key generation strategy to derive the key for the cache entry from the caching-enabled (@Cacheable or @CachePut annotated) service or repository methods based on the arguments to the method.

Of course, you can customize key generation used by any cache-enabled bean methods by 1) implementing the KeyGenerator interface and 2) declaring the KeyGenerator bean in your caching annotations, like so:

class ListKeyGenerator implements KeyGenerator {

  public Object generate(Object target, Method method, Object... params) {

    List&lt;?&gt; list = params[0];

    // use the list to generate a (unique) key for the cache entry
    Object generatedKey = ...;

    return generatedKey;
  }
}

> NOTE: The target is the Spring managed bean containing your updateAll(..) and findByIds(..) methods. The Method ref is the cache-enabled method invoked, such as updateAll(..). And, the array of Object params are the arguments (e.g. the List) to the cache-enabled methods.

> TIP: For simplicity and clarity (recommended), you may want to have 2 independent List-based KeyGenerators since 1 is a List of IDs (Long-typed; in the finder/query method) and the other is a List of ProjectTeam instances (in the update).

In configuration, you would declare:

@Configuration
class ApplicationCacheConfiguration {


  @Bean
  KeyGenerator listKeyGenerator() {
    return new ListKeyGenerator();
  }
}

You would apply the custom KeyGenerator using:

@Service
class ProjectTeamService {

  @CachePut(cacheNames = &quot;projectTeams&quot; keyGenerator = &quot;listKeyGenerator&quot;)
  public List&lt;ProjectTeam&gt; updateAll(List&lt;ProjectTeam&gt; projectTeams) {
    // perform any preprocessing steps as necessary
    return this.projectTeamRepository.saveAll(projectTeams);
  }
}

> NOTE: See corresponding Javadoc for @Cacheable and Javadoc for @CachePut.

However, you do not necessarily need a custom KeyGenerator if you simply rely on Spring's default key generation strategy as I mentioned to begin with.

Regarding the approach you chose, this will not work since it is trying to use a Spring SpEL expression to access a single element/item (i.e. a single ProjectTeam instance) in a List of ProjectTeam objects. Same applies for the List of IDs.

Which element from the List do you use? Do you use all elements (which would be difficult to aggregate using SpEL)? What happens if the List argument to the cache-enabled method is empty, or worse, null?

> TIP: You should review Spring's SpEL capabilities for more details.

These sort of concerns are more appropriately handled in your custom KeyGenerator implementation.

You should also be aware that the cached entries for the cache-enabled methods of your Spring managed bean will be (using "default key generation"):

cache-enabled method          | cache key         | cache value
----------------------------------------------------------------------
findByIds(:List&lt;Long&gt;)        | List&lt;Long&gt;        | List&lt;ProjectTeam&gt;
updateAll(:List&lt;ProjectTeam&gt;) | List&lt;ProjectTeam&gt; | List&lt;ProjectTeam&gt;

If you are expecting the individual elements/items (e.g. either Long IDs or ProjectTeam instances) of the Lists to be broken out and cached individually in separate cache entries, then you are mistaken because this is NOT how Spring's Cache Abstraction works by default.

The reason I mention this is because it is sort of implied by your (attempted) caching configuration as declared in the annotations.

Spring takes the argument(s) to the cache-enabled method (e.g. @Cacheable annotated method) and uses that as a key for the cache entry. The return value of the method is used as the value. Since the cache-enabled methods take a List and return a List then the cache entry key is the List argument and the List return value is the cache entry value.

As you might imagine, with each different List of IDs or List of ProjectTeam instances requested, your cache memory space is going to fill up fast! That is because if the List in each request (cache-enable method) just differs by 1 element, then that is going to constitute a new cache entry in the cache. Depending on how many (different) requests (invocations) your application processes, and the frequency, this could lead to a serious problem (e.g. OOME)!

If you need individual cache entries for individually requested items, then you should have a look at this SO post from earlier.

Good luck!

huangapple
  • 本文由 发表于 2023年3月7日 16:27:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/75659529.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定