【问题标题】:How to get individual item by key from cache in Spring cache in Spring Boot?如何从 Spring Boot 中的 Spring 缓存中的缓存中按键获取单个项目?
【发布时间】:2021-01-13 23:12:49
【问题描述】:

我们在我们的项目中添加了spring-boot-starter-cache,并且没有使用任何特定的缓存提供程序实现。我们正在通过调用以下方法在应用程序启动期间加载所有数据:

@Override
@Cacheable(cacheNames = "foos")
public List<FooDto> getAllFoo() {
    return fooRepository.findAll().stream()
            .map(FooEntityDomainToDtoMapper::mapDomainToDto) // mapping entity to dto
            .collect(Collectors.toList());
}

//Want to implement something like:
    public FooDto getFoo(Long id) {
    //return single object from foos(which are cached in above method)
    }

它将所有foos 存储在缓存中。正如预期的那样,下次我们调用getAllFoo 时,它是从缓存中返回而不是从数据库中返回。现在下次当用户通过 id 请求单个对象时,我们希望从这个已经缓存的 foos 数据中返回它,而不是调用 JPA 的 findById()。有什么方法可以实现吗?

【问题讨论】:

  • 可以直接访问CacheManager。对您来说问题是您需要自动装配它,而您不能在接口中这样做,因为对于 JPA,Spring 正在运行时生成 repo 实现。因此,您需要一些委托给 @Repository 的包装器组件,您可以在其中自动装配缓存管理器,并在必要时跳过 JPA 调用

标签: java spring spring-boot caching spring-cache


【解决方案1】:

您是否有任何理由想要或需要将所有 Foos 缓存在您的应用程序中,而不是单独缓存?

请记住,Spring 的缓存抽象在设计上使用方法参数(如果有)作为键,返回值作为缓存条目的值。如果方法没有参数,那么 Spring 会为你生成一个 id。

我有 written 关于如何自定义 Spring 的 CacheManager 实现以单独缓存@Cacheable 方法返回的值的集合

但是,目前,假设您确实需要/想要缓存 Foos 的整个列表。

然后,要创建一个方法,通过 ID 从 Foos 的“缓存”列表中提取单个 Foo,您可以,给定服务类中的原始缓存方法,例如...

@Sevice
class MyFooService {

  private final FooRepository<Foo, Long> fooRepository;

  @Cacheable(cacheNames = "foos")
  public List<FooDto> getAllFoos() {
    return this.fooRepository.findAll().stream()
      .map(FooEntityDomainToDtoMapper::mapDomainToDto) // mapping entity to dto
      .collect(Collectors.toList());
  }
}

然后,在另一个应用程序组件中,您可以...

@Component
class MyFooAccessor {

  private final MyFooService fooService;

  MyFooAccessor(MyFooService fooService) {
    this.fooService = fooService;
  }

  Optional<FooDto> getById(Long id) {
    this.fooService.getAllFoos().stream()
      .filter(fooDto -> fooDto.getId().equals(id))
      .findFirst();
  }

  ...

}

MyFooAccessor 确保您规避 缓存代理(即 AOP 代理 + 围绕 MyFooService 应用的缓存建议 Spring)。如果getById(..) 方法是MyFooService 类的成员,并且直接调用getAllFoos() 方法,那么您将绕过代理和缓存建议,从而导致每次访问数据库。

注意:如果您想保留 @987654336,您可以使用 Spring AOP Load Time Weaving (LTW)(参见 doc)来避免绕过缓存代理MyFooService 类中的 @ 方法与 getAllFoos()@Cacheable 方法。不过……

通常,您可以通过使用适当的设计模式适当地(重新)构造代码来解决这类问题。这也不是这里唯一的解决方案。 Spring 的美妙之处在于它为您提供了多种选择。这只是一种选择。

希望这可以帮助您获得更多想法。

【讨论】:

  • 我问第一个问题的原因是因为“缓存”所有项目会变得相当昂贵。请记住,对于大多数缓存系统来说,缓存通常在内存中(当然,有溢出、过期、驱逐甚至持久性策略,具体取决于缓存提供者),但是你在内存中保存的越多,GC 压力和停顿。小心点。
  • 大多数时候最好根据需要缓存经常使用的项目,懒惰。使用启发式、逻辑推理或简单关系,如果工作流足够复杂,还可以根据当前正在访问的对象预先缓存您知道用户接下来将访问的其他对象。无论如何,你会知道该怎么做。
  • 谢谢@john-blum。原因是我们想使用系统启动加载所有数据,方法是使用 PostConstruct 之类的东西,然后想使用缓存对象中的键获取单个对象。我考虑过使用列表中的流,但每次用户请求一个对象时,我都必须迭代整个流。因此,我通过将 fooRepository.findAll() 返回的列表转换为 Map,在 getAllFoos() 方法中返回了 。我想知道 Spring 中是否有某种现成的(或本机)方法可以使用某些键/ID 从一堆缓存数据(如本例中的“foos”)中读取。
  • 鉴于此描述,我可能会以不同的方式处理它。似乎您正在尝试“预先警告”缓存。如果是这样,那么您可以做两件事中的一件。 1) 注入CacheManager(或从ApplicationListener 中的ApplicationListener 中检索ContextRefreshedEvent,如上述Michael 所述),按名称获取适当的Cache(例如"FoosById") 并使用Repository 加载缓存。然后只需提供@Cacheable("FoosById") FooDto getByFooId(Long id)。或者...
  • 2) 无需注入或查找CacheManager,只需将@CachePut("FoosById") save(:FooDto) 方法添加到ApplicationListener 中的托管MyFoosService 类,并为由返回的每个FooDto 调用该方法侦听器内部的Repository.findAll() 方法。让我演示一下……
【解决方案2】:

使用密钥缓存对象,以便在从缓存中检索时可以使用该密钥。

@Override
@Cacheable(value = "fooByIDCache", key = "#id", unless = "#result == null")
public FooDto getFooByID(String id, FooDto fooDTO) {
    return fooDTO;
}

【讨论】:

  • 对于通过 id 获取,我们不希望提供单独的数据库调用。我们已经使用 findAll() 方法获取了所有值,并且对于后续的 id 请求,希望从已经缓存的对象中返回。您如何在上述方法中从缓存中返回值?
  • 您的方法返回列表,您必须迭代该方法并按 id 过滤并获取元素。我的答案是通过 id 缓存每个元素。
  • 虽然这将是首选方法(正如我在上面的回答中提到的那样,懒惰地缓存单个 Foos),但如果需要缓存所有 Foos(即在 getAllFoos() 方法中) ),那么您将双重缓存对象...每个 Foo 的 1 个条目(按 ID)和 List 启动时的第二个条目。
猜你喜欢
  • 1970-01-01
  • 2021-10-10
  • 2016-12-31
  • 2021-01-07
  • 1970-01-01
  • 2016-04-07
  • 1970-01-01
  • 1970-01-01
  • 2021-11-10
相关资源
最近更新 更多