Spring Caching不适用于findAll方法 [英] Spring Caching not working for findAll method

查看:78
本文介绍了Spring Caching不适用于findAll方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近开始着手缓存某种方法的结果。我正在使用@Cacheable和@CachePut来实现所需的功能。

I have recently started working on caching the result from a method. I am using @Cacheable and @CachePut to implement the desired the functionality.

但是以某种方式,保存操作并未更新findAll方法的缓存。下面是相同的代码段:

But somehow, the save operation is not updating the cache for findAll method. Below is the code snippet for the same:

@RestController
@RequestMapping(path = "/test/v1")
@CacheConfig(cacheNames = "persons")
public class CacheDemoController {

    @Autowired
    private PersonRepository personRepository;

    @Cacheable
    @RequestMapping(method = RequestMethod.GET, path="/persons/{id}")
    public Person getPerson(@PathVariable(name = "id") long id) {
        return this.personRepository.findById(id);
    }

    @Cacheable
    @RequestMapping(method = RequestMethod.GET, path="/persons")
    public List<Person> findAll() {
        return this.personRepository.findAll();
    }

    @CachePut
    @RequestMapping(method = RequestMethod.POST, path="/save")
    public Person savePerson(@RequestBody Person person) {
        return this.personRepository.save(person);
    }
}

第一次调用findAll方法时,它将结果存储在人员缓存中,并且对于所有后续调用,即使在两者之间执行了save()操作,它也返回相同的结果。

For the very first call to the findAll method, it is storing the the result in the "persons" cache and for all the subsequent calls it is returning the same result even if the save() operation has been performed in between.

我对缓存非常陌生,因此对此的任何建议都将对您有所帮助。

I am pretty new to caching so any advice on this would be of great help.

谢谢!

推荐答案

因此,关于您的UC和上面的代码,我想到了一些事情。

So, a few things come to mind regarding your UC and looking at your code above.


  1. 首先,我不喜欢在应用程序的UI或数据层中启用缓存的用户,尽管在数据层(例如DAO或Repos)中更有意义。缓存(如事务管理,安全性等)是服务级别的问题,因此属于服务层IMO,其中您的应用程序包括:[Web | Mobile | CLI] + UI->服务-> DAO(又名Repo)。在服务层中启用缓存的优点是在您的应用程序/系统体系结构中更可重用。想想,除了为Web服务之外,还为移动应用程序客户端提供服务。您的Web控制器级别不一定与处理Mobile应用程序客户端的控制器相同。

  1. First, I am not a fan of users enabling caching in either the UI or Data tier of the application, though it makes more sense in the Data tier (e.g. DAOs or Repos). Caching, like Transaction Management, Security, etc, is a service-level concern and therefore belongs in the Service tier IMO, where your application consists of: [Web|Mobile|CLI]+ UI -> Service -> DAO (a.k.a. Repo). The advantage of enabling Caching in the Service tier is that is is more reusable across your application/system architecture. Think, servicing Mobile app clients in addition to Web, for instance. Your Controllers for you Web tier may not necessarily be the same as those handling Mobile app clients.

我鼓励您阅读Spring框架参考文档核心rel = noreferrer> Spring的缓存抽象 。仅供参考, Spring的缓存抽象(类似于TX管理)深深植根于 Spring的 AOP支持。但是,出于您的目的,让我们根据发生的情况来分解 Spring Web MVC控制器(即 CacheDemoController )。

I encourage you to read the chapter in the core Spring Framework's Reference Documentation on Spring's Cache Abstraction. FYI, Spring's Cache Abstraction, like TX management, is deeply rooted in Spring's AOP support. However, for your purposes here, let's break your Spring Web MVC Controller (i.e. CacheDemoController) down a bit as to what is happening.

因此,您有一个正在缓存的 findAll()方法

So, you have a findAll() method that you are caching the results for.


警告:另外,我一般不建议您缓存 Repository.findAll()的结果调用,尤其是在生产中!尽管在有限的数据集下这可能在本地就可以了,但是 CrudRepository.findAll()方法在数据结构中返回 all 结果特定对象/数据类型(例如)在备份数据存储中(例如RDBMS中的表) )默认情况下,除非您对返回的结果集使用分页或某些LIMIT。当涉及到缓存时,请始终考虑在相对少见的数据更改上进行高度重用。

WARNING: Also, I don't generally recommend that you cache the results of a Repository.findAll() call, especially in production! While this might work just fine locally given a limited data set, the CrudRepository.findAll() method returns all results in the data structure in the backing data store (e.g. the Person Table in an RDBMS) for that particular object/data type (e.g. Person) by default, unless you are employing paging or some LIMIT on the result set returned. When it comes to caching, always think a high degree of reuse on relatively infrequent data changes; these are good candidates for caching.

鉴于您控制器的 findAll()方法具有方法参数, Spring 将确定默认参数。用于缓存 findAll()方法的返回值的键(即 List< Person )。

Given your Controller's findAll() method has NO method parameters, Spring is going to determine a "default" key to use to cache the findAll() method's return value (i.e. List<Person).


提示:请参阅 默认密钥生成 有关更多详细信息。

TIP: see Spring's docs on "Default Key Generation" for more details.

注意:在 Spring 中,与缓存一样,键/值存储(例如 java.util.Map) )是 Spring的 缓存 。但是,并非所有的缓存提供者都可以使用。相等(例如,Redis与a java.util.concurrent.ConcurrentHashMap )。

NOTE: In Spring, as with caching in general, Key/Value stores (like java.util.Map) are the primary implementation's for Spring's notion of a Cache. However, not all "caching providers" are equal (e.g. Redis vs. a java.util.concurrent.ConcurrentHashMap, for instance).

调用 findAll()控制器方法后,您的缓存将具有...

After calling the findAll() Controller method, your cache will have...

KEY    | VALUE
------------------------
abc123 | List of People



注意:缓存将存储每个列表中的 Person 分别作为单独的缓存条目。这不是 Spring的Cache Abstraction 中方法级缓存的工作方式,至少默认情况下不是这样。但是,它是可能的

NOTE: the cache will not store each Person in the list individually as a separate cache entry. That is not how method-level caching works in Spring's Cache Abstraction, at least not by default. However, it is possible.

然后,假设接下来调用Controller的可缓存 getPerson(id:long)方法。嗯,这个方法包括一个参数,即 Person的 ID。当调用Controller getPerson(..)方法并时,此参数的参数将用作 Spring的缓存抽象中的键。 Spring 尝试在缓存中查找(可能存在的)值。例如,说用 controller.getPerson(1)调用该方法。高速缓存中不存在带有键1的高速缓存条目,即使 Person (1)位于映射到键 abc123 。因此, Spring 不会在列表中找到 Person 1并返回它,因此,此操作会导致缓存未命中。当该方法返回值时(ID为1的 Person )将被缓存。但是,缓存现在看起来像这样……

Then, suppose your Controller's cacheable getPerson(id:long) method is called next. Well, this method includes a parameter, the Person's ID. The argument to this parameter will be used as the key in Spring's Cache Abstraction when the Controller getPerson(..) method is called and Spring attempts to find the (possibly existing) value in the cache. For example, say the method is called with controller.getPerson(1). Except a cache entry with key 1 does not exist in the cache, even if that Person (1) is in list mapped to key abc123. Thus, Spring is not going to find Person 1 in the list and return it, and so, this op results in a cache miss. When the method returns the value (the Person with ID 1) will be cached. But, the cache now looks like this...

KEY    | VALUE
------------------------
abc123 | List of People
1      | Person(1)

最后,用户调用控制器的 savePerson(:Person)方法。同样, savePerson(:Person)控制器方法的参数值用作键(即 Person ; object)。假设方法被称为 controller.savePerson(person(1))。好吧, CachePut 会在方法返回时发生,因此 Person 1的现有缓存条目不会更新,因为; key是不同的,因此创建了一个新的缓存条目,并且您的缓存再次看起来像这样……

Finally, a user invokes the Controller's savePerson(:Person) method. Again, the savePerson(:Person) Controller method's parameter value is used as the key (i.e. a "Person" object). Let's say the method is called as so, controller.savePerson(person(1)). Well, the CachePut happens when the method returns, so the existing cache entry for Person 1 is not updated since the "key" is different, so a new cache entry is created, and your cache again looks like this...

KEY       | VALUE
---------------------------
abc123    | List of People
1         | Person(1)
Person(1) | Person(1)

其中一个可能都不是您想要或不想发生的事情。

None of which is probably what you wanted nor intended to happen.

因此,您如何解决此问题。好的,正如我在上面的警告中所提到的,您可能不应该缓存从操作返回的值的整个集合。而且,即使您这样做,也需要扩展Spring的缓存基础结构OOTB来处理 Collection 返回类型,以破坏 Collection 根据某个键进入单个缓存条目。

So, how do you fix this. Well, as I mentioned in the WARNING above, you probably should not be caching an entire collection of values returned from an op. And, even if you do, you need to extend Spring's Caching infrastructure OOTB to handle Collection return types, to break the elements of the Collection up into individual cache entries based on some key. This is intimately more involved.

但是,您可以在 getPerson(id:long)和<$ c之间添加更好的协调$ c> savePerson(:Person)控制器方法。基本上,您需要更详细地说明 savePerson(:Person)方法的密钥。幸运的是,Spring允许您指定通过提供自定义 KeyGenerator 实现,或仅通过使用SpEL即可。同样,请参阅文档以获取更多详细信息。

You can, however, add better coordination between the getPerson(id:long) and savePerson(:Person) Controller methods, however. Basically, you need to be a bit more specific about your key to the savePerson(:Person) method. Fortunately, Spring allows you to "specify" the key, by either providing s custom KeyGenerator implementation or simply by using SpEL. Again, see the docs for more details.

因此,可以像这样修改您的示例...

So your example could be modified like so...

@CachePut(key = "#result.id"
@RequestMapping(method = RequestMethod.POST, path="/save")
public Person savePerson(@RequestBody Person person) {
    return this.personRepository.save(person);
}

注意 @CachePut 批注和 key 属性包含 SpEL表达式。在这种情况下,我表示此控制器 savePerson(:Person)方法的缓存键应为返回值(即 #result)或 Person 对象的ID,从而匹配Controller getPerson(id:long)方法的键,该键随后将更新单个缓存条目键入人的 ID ...

Notice the @CachePut annotation with the key attribute containing the SpEL expression. In this case, I indicated that the cache "key" for this Controller savePerson(:Person) method should be the return value's (i.e. the "#result") or Person object's ID, thereby matching the Controller getPerson(id:long) method's key, which will then update the single cache entry for the Person keyed on the Person's ID...

KEY       | VALUE
---------------------------
abc123    | List of People
1         | Person(1)

不过,这不会处理 findAll()方法,但它适用于 getPerson(id) savePerson(:Person)。再一次,请参阅我的答案关于集合值的帖子作为Spring缓存基础结构中的返回类型以及如何正确处理它们。但小心点!将整个值集合作为单独的缓存条目进行缓存可能会严重破坏应用程序的内存占用量,从而导致OOME。您肯定需要调整在这种情况下(缓存,到期,压缩等),在将大量的完整内容放入缓存之前,尤其是在实际上可能同时发生成千上万个请求的UI层上,底层缓存提供程序(逐出,到期,压缩等)被并发。也成为一个因素!请参见同步功能

Still, this won't handle the findAll() method, but it works for getPerson(id) and savePerson(:Person). Again, see my answers to the posting(s) on Collection values as return types in Spring's Caching infrastructure and how to handle them properly. But, be careful! Caching an entire Collection of values as individual cache entries could reck havoc on your application's memory footprint, resulting in OOME. You definitely need to "tune" the underlying caching provider in this case (eviction, expiration, compression, etc) before putting a large deal of entires in the cache, particular at the UI tier where literally thousands of requests maybe happening simultaneously, then "concurrency" becomes a factor too! See Spring's docs on sync capabilities.

无论如何,希望这有助于您理解缓存,尤其是Spring,以及一般的缓存。

Anyway, hope this helps aid your understanding of caching, with Spring in particular, as well as caching in general.

干杯,
-约翰

Cheers, -John

这篇关于Spring Caching不适用于findAll方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆