我最近开始从一个方法缓存结果。我正在使用@Cacheable和@CachePut来实现所需的功能。
但不知何故,保存操作不会更新findAll方法的缓存。以下是相同的代码段:
@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方法,它将结果存储在&#34; person&#34;缓存和所有后续调用,即使在中间执行了save()操作,它也会返回相同的结果。
我对缓存很新,所以对此有任何建议都会有很大的帮助。
谢谢!
答案 0 :(得分:9)
因此,关于您的UC并查看上面的代码,我会想到一些事情。
首先,我不是在应用程序的UI或数据层中启用缓存的用户的粉丝,尽管它在数据层(例如DAO或Repos)中更有意义。缓存(如事务管理,安全性等)是服务级别的问题,因此属于服务层IMO,其中您的应用程序包括:[Web | Mobile | CLI] + UI - &gt;服务 - &gt; DAO(a.k.a.回购)。在服务层中启用缓存的优点是可以在您的应用程序/系统体系结构中重复使用。例如,想想,除了Web之外,还可以为移动应用客户端提供服务。您的控制器Web层可能不一定与处理移动应用客户端的控制器相同。
我建议您阅读Spring's Cache Abstraction上核心 Spring Framework的参考文档中的章节。与TX管理一样,FYI, Spring的缓存抽象深深植根于Spring's AOP support。但是,出于您的目的,让我们将您的 Spring Web MVC控制器(即CacheDemoController
)分解为发生了什么。
所以,你有一个findAll()
方法,你正在缓存结果。
警告:另外,我通常不建议您缓存
Repository.findAll()
来电的结果,尤其是在制作中!虽然在给定有限数据集的情况下这可能在本地工作正常,但CrudRepository.findAll()
method返回所有会导致支持数据存储中的数据结构(例如RDBMS中的Person
表)默认情况下,对于该特定对象/数据类型(例如Person
),除非您对返回的结果集使用分页或一些LIMIT。在缓存方面,总是考虑在相对不频繁的数据更改中进行高度重用;这些都是缓存的好选择。
鉴于您的Controller的findAll()
方法具有 NO 方法参数, Spring 将确定&#34;默认&#34;用于缓存findAll()
方法的返回值(即List<Person
)的密钥。
提示:请参阅Spring上的#34; Default Key Generation&#34;了解更多详情。
注意:在 Spring 中,与一般的缓存一样,键/值存储(如
java.util.Map
)是 Spring的主要实现方式Cache
的概念。但是,并非所有&#34;缓存提供商&#34;是相等的(例如,Redis与java.util.concurrent.ConcurrentHashMap
相比。)
调用findAll()
Controller方法后,您的缓存将有......
KEY | VALUE
------------------------
abc123 | List of People
注意:缓存将不将列表中的每个
Person
单独存储为单独的缓存条目。这不是方法级缓存在 Spring的缓存抽象中的工作方式,至少在默认情况下不是这样。但是,它是possible。
然后,假设接下来调用了Controller的可缓存getPerson(id:long)
方法。好吧,这个方法包含一个参数Person's
ID。当调用Controller getPerson(..)
方法并且 Spring 尝试查找时,此参数的参数将用作 Spring的高速缓存抽象中的键。缓存中的(可能存在的)值。例如,假设使用controller.getPerson(1)
调用该方法。除了缓存中的密钥1的缓存条目不存在,即使Person
(1)在列表中映射到密钥abc123
。因此, Spring 不会在列表中找到Person
1并返回它,因此,此操作会导致缓存未命中。当方法返回值时(ID为1的Person
)将被缓存。但是,缓存现在看起来像这样......
KEY | VALUE
------------------------
abc123 | List of People
1 | Person(1)
最后,用户调用Controller的savePerson(:Person)
方法。同样,savePerson(:Person)
控制器方法的参数值被用作密钥(即&#34; Person
&#34;对象)。我们假设该方法被称为controller.savePerson(person(1))
。好吧,当方法返回时发生CachePut
,因此Person
1的现有缓存条目不会更新,因为&#34;键&#34;是不同的,所以创建了一个新的缓存条目,你的缓存再次看起来像这样......
KEY | VALUE
---------------------------
abc123 | List of People
1 | Person(1)
Person(1) | Person(1)
这些都不是你想要或不打算发生的事情。
那么,你如何解决这个问题。好吧,正如我在上面的警告中所提到的,你可能不应该缓存从op返回的整个值集合。而且,即使您这样做,也需要扩展Spring的缓存基础结构OOTB来处理Collection
返回类型,以便根据某些键将Collection
的元素分解为单个缓存条目。这非常密切。
但是,您可以在getPerson(id:long)
和savePerson(:Person)
控制器方法之间添加更好的协调。基本上,您需要更加具体地了解savePerson(:Person)
方法的密钥。幸运的是,Spring允许你指定&#34;关键,通过提供自定义KeyGenerator
实现或简单地使用SpEL。再次,see the docs了解更多详情。
所以你的例子可以这样修改......
@CachePut(key = "#result.id"
@RequestMapping(method = RequestMethod.POST, path="/save")
public Person savePerson(@RequestBody Person person) {
return this.personRepository.save(person);
}
请注意@CachePut
注释,其key
属性包含 SpEL表达式。在这种情况下,我表示缓存&#34;键&#34;对于此Controller savePerson(:Person)
方法应该是返回值(即&#34;#result&#34;)或Person
对象的ID,从而匹配Controller {{ 1}}方法的密钥,然后将更新getPerson(id:long)
ID上的Person
的单个缓存条目...
Person's
但是,这不会处理KEY | VALUE
---------------------------
abc123 | List of People
1 | Person(1)
方法,但适用于findAll()
和getPerson(id)
。再次,请查看我的answers有关Collection值的帖子作为Spring的缓存基础结构中的返回类型以及如何正确处理它们。不过要小心!将整个值集合作为单独的缓存条目进行缓存可能会严重破坏应用程序的内存占用,从而导致OOME。你肯定需要调整&#34;在这种情况下的底层缓存提供程序(驱逐,到期,压缩等),然后在缓存中放入大量的内容,特别是在UI层,其中数千个请求可能同时发生,然后&#34;并发&#34;也成为一个因素!请参阅sync capabilities上的Spring文档。
无论如何,希望这有助于您理解缓存,尤其是Spring,以及一般的缓存。
干杯, -John