1

I have a simple service in a springboot application, one of the methods gets an object from the database by Id(Primary key). I also have another method that returns all the objects in that table.

@Cacheable("stores")
public List<Store> findAllStores() throws InterruptedException {
    Thread.sleep(5000);
    return storeRepository.findAll();
}

@Cacheable("stores")
public Store findById(int storeId) throws InterruptedException {
    Thread.sleep(5000);
    return storeRepository.findById(storeId).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "No store with specified ID exist"));
}

In my tests I called the first method, which populated the cache called "stores" with all the store objects from the table, but when I call the second method to individually find a store by Id, i still get the 5 second wait time? Why is this happening? There are no errors and cache is not a variable so i'm finding it really hard to debug this problem, as I cant see the contents of the cache during debug session.

Oirampok
  • 569
  • 5
  • 23

1 Answers1

1

Any data stored in a cache requires a key for its speedy retrieval. Spring, by default, creates caching keys using the annotated method's signature as key.

So here you are basically using two different methods and they will be using separate keys which is method signature. So if you call the same method again only then the result will be returned from cache instead of getting data from DB. I hope you get that. You can check below link

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/annotation/Cacheable.html#:~:text=Annotation%20Type%20Cacheable&text=Annotation%20indicating%20that%20the%20result,invoked%20for%20the%20given%20arguments.

And your expectation is totally wrong, as told above each cache needs a cacheKey and it stores values against that key, if you execute your second method - first time you get Store from storeId and it's saved in cache with method signature as key which will also have storeId as key, so second time you call the same method it will get it from cache since it already has entry for that key. the first method and second method key's are different so your expectation will be wrong.

<---->Edit<--->

You can modify your code like below, specifying the cacheID explicitly.

@Cacheable("stores",key = "#storeId")
public Store findById(int storeId) throws InterruptedException {
   
    return storeRepository.findById(storeId).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "No store with specified ID exist"));
}

Remove the findAllStores() method its not necessary. And use directly cacheManager to store the values. Something like below.

@Autowired
 CacheManager cacheManager;
@Autowired
 StoreRepository repo;

public void loadCache() {
  for(Store store:repo.getAll()) {
    cacheManager.getCache("stores").put(store.getId(),store);
  }
}

You have to call this on startUp. That's it.

Lucia
  • 791
  • 1
  • 8
  • 17
  • I see, so if im getting it right, the same cache "stores" contains two separate caches with different keys. Is there a way to make them both use the same key in one cache then? – Oirampok May 24 '21 at 19:26
  • @Oirampok The things are stored in Cache like a map using key value pairs. In your case the first method getALL() returns list of values and the second one is getByID. So the types itself are different, so i doubt if there is anything. But you can check if you can customize the way when it's stored in the cache – Lucia May 25 '21 at 05:32
  • But what exactly is your requirement it looks strange. You want to getAll() and then store in cache so that next time onwards you want to refer from cache. When exactly are you trying to invalidate cache? And what exactly is the limit of this getAll() definitely you dont want to put all things in DB on cache. – Lucia May 25 '21 at 05:42
  • im building a point of sale system for a retail chain, they only have 20 stores, right now every time a new sale transaction occurs we call the DB to get that store in which the sale occured. Since the amount of stores and the details of each store will almost never change, what im trying to do to make the application more efficient and reduce the amount of backend api calls, is to create a cache of stores and populate it with all 20 stores on startup. Therefore every time a new sale comes in we don't have to call DB to get a store entry. Is this a bad way of going about it? – Oirampok May 26 '21 at 19:05
  • Your usecase looks fine. The solutions is to getAll stores and then load the cache one by one with storeId as key. So that when you call getStore() by storeId, the cachemanager first looks the cache by storeId and then returns it. See first thing cache is nothing but key, value pairs - how you store things on Map for easy lookup right? cache is same. I have edited the answer above that works for your usecase. – Lucia May 28 '21 at 07:00
  • thank you, your solution works perfectly. – Oirampok May 28 '21 at 19:16