Spring Caching不适用于findAll方法 [英] Spring Caching not working for findAll method
问题描述
我最近开始着手缓存某种方法的结果。我正在使用@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.
-
首先,我不喜欢在应用程序的UI或数据层中启用缓存的用户,尽管在数据层(例如DAO或Repos)中更有意义。缓存(如事务管理,安全性等)是服务级别的问题,因此属于服务层IMO,其中您的应用程序包括:[Web | Mobile | CLI] + UI->服务-> DAO(又名Repo)。在服务层中启用缓存的优点是在您的应用程序/系统体系结构中更可重用。想想,除了为Web服务之外,还为移动应用程序客户端提供服务。您的Web控制器级别不一定与处理Mobile应用程序客户端的控制器相同。
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.
我鼓励您阅读 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, theCrudRepository.findAll()
method returns all results in the data structure in the backing data store (e.g. thePerson
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 $的列表中c $ c>。因此, 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 $的元素c $ c>根据某个键进入单个缓存条目。
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屋!