当前位置: 首页 > 知识库问答 >
问题:

Spring缓存不适用于findAll方法

周正真
2023-03-14

我最近开始缓存一个方法的结果。我使用@Cacheable和@CachePut来实现所需的功能。

但不知何故,save操作并没有更新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方法的第一个调用,它将结果存储在“persons”缓存中,对于所有后续调用,它将返回相同的结果,即使在两者之间执行了save()操作。

我对缓存很陌生,所以任何关于这方面的建议都会很有帮助。

谢谢

共有1个答案

宗项禹
2023-03-14

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

>

  • 首先,我不喜欢用户在应用程序的UI或数据层中启用缓存,尽管它在数据层(例如DAOs或Repos)中更有意义。缓存,如事务管理、安全性等,是一个服务级别问题,因此属于服务层IMO,您的应用程序由:[Web | Mobile | CLI]UI组成-

    我鼓励您阅读核心Spring框架参考文档中有关Spring缓存抽象的一章。仅供参考,Spring的缓存抽象与TX管理一样,深深植根于Spring的AOP支持。然而,出于您的目的,让我们将Spring Web MVC控制器(即CacheDemoController)稍微分解一下,看看发生了什么。

    因此,您有一个缓存结果的方法。

    警告:此外,我通常不建议您缓存存储库的结果。findAll()调用,尤其是在生产中!虽然在局部给定有限的数据集时,这可能会很好地工作,但CrudRepository。默认情况下,findAll()方法返回特定对象/数据类型(例如,Person)的备份数据存储(例如RDBMS中的Person表)中数据结构中的所有结果,除非对返回的结果集使用分页或某些限制。说到缓存,总是要考虑对相对不频繁的数据更改进行高度重用;这些都是缓存的好候选者。

    给定Controller的findAll()方法没有方法参数,Spring将确定一个“默认”键来缓存findAll()方法的返回值(即List

    提示:有关更多详细信息,请参阅Spring关于“默认密钥生成”的文档。

    注意:在Spring中,与一般的缓存一样,键/值存储(如java.util.Map)是Spring缓存概念的主要实现。然而,并非所有的“缓存提供者”都是平等的(例如,Redis与java.util.concurrent.ConcurrentHashMap)。

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

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

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

    然后,假设接下来调用控制器的cacheablegetPerson(id:long)方法。这个方法包括一个参数,即人的ID。当控制器getPerson(…)时,这个参数的参数将用作Spring缓存抽象中的键 方法,Spring尝试在缓存中查找(可能存在)值。例如,假设使用控制器调用该方法。getPerson(1)。除非缓存中不存在键为1的缓存项,即使该人(1)在映射到键abc123的列表中。因此,Spring不会在列表中找到Person并返回它,因此,该操作会导致缓存未命中。当该方法返回值时(ID为1的人)将被缓存。但是,缓存现在看起来像这样。。。

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

    最后,用户调用控制器的savePerson(:Person)方法。同样,savePerson(:Person)控制器方法的参数值用作键(即“Person”对象)。假设该方法被称为控制器。savePerson(person(1))。好的,当方法返回时会发生缓存put,因此Person的现有缓存项不会更新,因为“key”不同,所以会创建一个新的缓存项,您的缓存再次如下所示。。。

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

    这些可能都不是你想要或打算发生的。

    那么,你如何解决这个问题呢。正如我在上面的警告中提到的,您可能不应该缓存从操作返回的整个值集合。即使这样,您也需要扩展Spring的缓存基础结构OOTB来处理集合返回类型,根据某个键将集合的元素分解为单个缓存项。这是密切相关的。

    但是,您可以在getPerson(id:long)和savePerson(:Person)控制器方法之间添加更好的协调。基本上,您需要更具体地说明savePerson(:Person)方法的键。幸运的是,Spring允许您通过提供自定义的键生成器实现或简单地使用SpEL来“指定”键。请再次参阅文档以了解更多详细信息。

    所以你的例子可以这样修改...

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

    请注意带有包含SpEL表达式的key属性的@Cacheput注释。在这种情况下,我指出此ControllersavePerson(: Person)方法的缓存“key”应该是返回值的(即“#结果”)或Person对象的ID,从而匹配ControllergetPerson(id: long)方法的键,然后它将更新键控在PersonID上的Person的单个缓存条目...

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

    尽管如此,这仍然无法处理findAll()方法,但它适用于getPerson(id)savePerson(: Person)。同样,请参阅我对Spring的缓存基础架构中将Collection值作为返回类型发布的答案以及如何正确处理它们。但是,要小心!将整个值集合缓存为单个缓存条目可能会对应用程序的内存占用造成严重破坏,从而导致OOME。在这种情况下,在将大量实体放入缓存之前,您肯定需要“调整”底层缓存提供程序(驱逐、过期、压缩等),尤其是在用户界面层,那里可能同时发生数千个请求,那么“并发”也成为一个因素!请参阅Spring关于同步功能的文档。

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

    干杯-约翰

  •  类似资料:
    • 下面是我的自定义注释。 我想用“MyAnnoation”做点什么,所以我声明了和如下方法。 下面的服务被其他类“自动安装”。所以我认为这不是与AOP代理相关的问题。 下面的代码调用上面的服务 如果将注释到一个类,则会调用procedure(),但如果一个方法带有类似于上述代码的注释,则该方法不起作用。我希望它只使用方法。 我想解决什么?

    • 我得到错误 我有JPA Hibernate配置和使用Eh缓存的查询缓存和二级缓存。 配置:PostgreSQL 9.6 JPA 2.1 Hibernate 5.2.3。最终的 我正在尝试使用SqlResultSetMapping[自定义结果集类]执行NativeQuery。当我禁用缓存时,一切正常。 但是,在启用缓存时出现上述错误。除了NativeQuery,缓存工作正常。 表架构: SQL本机查

    • 我有一个方法可以调用另一个@Cacheable方法,如下所示: 如果我直接调用findomeod1(),缓存工作得很好。但是,当我调用findomeod2()时,findomeod1()上的缓存被完全忽略。 这可能是JVM将findMethod1()内联到findMethod2()中的伎俩吗? 有没有人遇到过类似的问题? 谢谢

    • 您好,我正在尝试使用PostConstruct方法初始化字段,但在测试中,此方法不会填充bidiMap字段。 有没有办法模拟字段,它是的字段? 测试: 正在测试的类:

    • 起初,我在使用< code>writeAndFlush(...)直到我偶然发现了这个修复。现在,我已经可以使用< code>writeAndFlush(...)每个字符串都带有后缀/r/n。当我尝试发送< code>ByteBuf对象时,问题仍然存在。显然,我不能只在消息末尾添加/r/n。对此有什么解决办法吗? 如果有帮助,我将使用

    • 但是当我的method2被调用时,AOP功能没有被调用,即没有调用AOPLogger类的checkAccess方法。 我错过什么了吗?