3

We have spring-boot-starter-cache added in our project and not using any particular implementation of cache provider. we are loading all the data during application startup by calling following method:

@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)
    }

It is storing all foos in the cache. And as expected next time when we are calling getAllFoo, it is returning from the cache rather than returning from the database. Now next time when user request for individual object by id, we want to return it from this already cached foos data rather than calling findById() of JPA. Is there any way to achieve this?

implosivesilence
  • 536
  • 10
  • 24
  • You can access the CacheManager directly. Problem for you is that you need to autowire it, and you cannot do that in an interface, because for JPA Spring is generating the repo implementation at runtime. So you need some wrapper component which delegates to your @Repository, where you can autowire the cache manager, and skip the JPA call if necessary – Michael Sep 28 '20 at 11:17

2 Answers2

4

Is there any reason you want to, or need to, cache all Foos in your application collectively rather than individually?

Keep in mind, it is by design that Spring's Cache Abstraction uses the method parameter(s) (if any) as the key and the return value as the value of the cache entry. If the method has no parameters then Spring will generate an id for you.

I have written about how to customize Spring's CacheManager implementation to cache a Collection of values returned by a @Cacheable method, individually.

However, for the moment, let's assume you do need/want to cache the entire List of Foos.

Then, to create a method that pulls an individual Foo by ID from the "cached" List of Foos, you could, given your original cached method in a service class, do, for example...

@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());
  }
}

Then, in another application component, you could...

@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();
  }

  ...

}

The MyFooAccessor makes sure you do not circumvent the caching proxy (i.e. the AOP Proxy + Caching Advice around the MyFooService applied by Spring). If the getById(..) method were a member of the MyFooService class, and called the getAllFoos() method directly, you would circumvent the proxy and caching advice resulting in a database access each time.

NOTE: You could use Spring AOP Load Time Weaving (LTW) (see doc) to avoid circumventing the caching proxy if you want to keep the getById(:Long) method in the MyFooService class with the getAllFoos(), @Cacheable method. However...

Typically, you can solve these sort of problems by (re-)structuring your code appropriately, using the proper design pattern. This is not the only solution here, either. That is the beautiful thing about Spring is that it gives you many choices. This is just but 1 choice.

Hope this helps give you more ideas.

John Blum
  • 7,381
  • 1
  • 20
  • 30
  • The reason I ask the first question is because it can get rather expensive to "cache" all items. Remember, the cache is typically in-memory for most caching systems (of course, with overflow, expiration, eviction and even persistence policies, depending on the caching provider), but the more you keep in memory, the more potential for GC pressure and pauses. Just be careful. – John Blum Sep 28 '20 at 19:17
  • Most of the time it is better to cache frequently used items as needed, lazily. Using heuristics, logical deduction, or simple relationships, it could also be possible to pre-cache other objects you know will be accessed by the user next based on the object currently being accessed, if the workflow is sufficiently complex. Anyway, you will know what to do. – John Blum Sep 28 '20 at 19:22
  • Thanks @john-blum. The reason is we want to load all data using system start by using something like PostConstruct and later on want to fetch individual objects using key from that cached objects. I thought about using the stream on list but every time user request for one object i have to iterate entire stream. So i returned in getAllFoos() methods by converting list returned by fooRepository.findAll() into the Map. I was wondering if there's some kinda ready-made(or native) method available in Spring to read from bunch of cached data(like 'foos' in this case) using some key/id. – implosivesilence Sep 29 '20 at 06:59
  • Given this description, I'd probably approach it differently then. Seems like you are trying to "pre-warn" the cache. If so, then you can do 1 of 2 things. 1) Inject the `CacheManager` (or retrieve it from the `ApplicationContext` in a `ApplicationListener` listening to the `ContextRefreshedEvent` as **Michael** described above), get the appropriate `Cache` by name (e.g. "FoosById") and use the `Repository` to load the cache. Then simply provide a `@Cacheable("FoosById") FooDto getByFooId(Long id)`. Or... – John Blum Sep 29 '20 at 18:41
  • 2) Rather than inject or lookup the `CacheManager`, simply add a `@CachePut("FoosById") save(:FooDto)` method to the managed `MyFoosService` class in an `ApplicationListener` and call that method for each `FooDto` returned by the `Repository.findAll()` method inside the listener. Let me demonstrate... – John Blum Sep 29 '20 at 18:45
  • And here is an example/test class demonstrating the 2 approaches I described above in the comments... https://github.com/jxblum/stackoverflow-questions-answers/blob/master/spring-cache/src/test/java/io/stackoverflow/questions/springcache/PreWarmCacheIntegrationTests.java – John Blum Sep 30 '20 at 00:05
  • You can switch between the two approaches (1 & 2) by changing the *Spring Profile* used by the test class, but I'd prefer the "*PreWarmCacheUsingService*" in your use case, if it were me. Hope this all helps. – John Blum Sep 30 '20 at 00:06
  • Thanks @john-blum. If I want to keep the getById(..) method in the same MyFooService then gotta use Load Time Weaving (LTW), can you please help with this? like how? What are downside of this approach? – implosivesilence Sep 30 '20 at 11:22
  • Also, we cannot save to the backend using 'XRepository.save()' as it's lookup data which is already present in database. – implosivesilence Sep 30 '20 at 11:33
  • If I were to proceed using 'MyFooAccessor', which design pattern should we use? – implosivesilence Sep 30 '20 at 16:03
  • Regarding LTW, see here... https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-aj-ltw. No downsides (necessarily), just trade-offs. Keep in mind that LTW modifies the byte code of your managed components at runtime. AspectJ does this via a Java Agent, but Spring can do this with an additional JAR on the application classpath. – John Blum Sep 30 '20 at 17:06
  • Regarding `Repository.save(..)`... exactly! This is why I implemented the `cache(..)` method instead (https://github.com/jxblum/stackoverflow-questions-answers/blob/master/spring-cache/src/test/java/io/stackoverflow/questions/springcache/PreWarmCacheIntegrationTests.java#L268-L271). The `cache(..)` method is what is used to preload the cache in the listener (https://github.com/jxblum/stackoverflow-questions-answers/blob/master/spring-cache/src/test/java/io/stackoverflow/questions/springcache/PreWarmCacheIntegrationTests.java#L182). – John Blum Sep 30 '20 at 17:09
  • With `MyFooAccessor`, that allows you to avoid LTW. But it requires you to carefully structure your code in such a way that you are always accessing the PROXY of the `@Cacheable` `FooService` to invoke the caching logic (i.e. AOP Advice). There really is no right or wrong approach to this. It is merely preference. Choose what makes the most sense for your UC, requirements, etc, and more importantly, your understanding from a maintenance point of view. Simplicity is always better than clever, IMO. – John Blum Sep 30 '20 at 17:09
0

Cache the object by using the key so while retrieving from the cache you can use that key.

@Override
@Cacheable(value = "fooByIDCache", key = "#id", unless = "#result == null")
public FooDto getFooByID(String id, FooDto fooDTO) {
    return fooDTO;
}
Vaibs
  • 1,546
  • 9
  • 31
  • for fetching by id we do not wish to give separate database call. we have fetched all the values using findAll() method and for subsequent request by id, want to return from the already cached objects. how you are returning values from the cache in above method? – implosivesilence Sep 29 '20 at 11:13
  • Your method return list, you have to iterate on that method and filter by id and get the element. My answer is for caching every element by id. – Vaibs Sep 29 '20 at 13:55
  • While this would be the preferred approach (lazily caching individual `Foos` as I alluded to in my answer above), if there is a need for caching all `Foos` (i.e. in `getAllFoos()` method), then you would be double caching the objects... 1 entry for each `Foo` by ID & a 2nd entry for the `List` on startup. – John Blum Sep 29 '20 at 18:33