0

I am newbie at Caffeine and Spring boot. I am trying to find a solution to cache a list of objects. the DB transaction takes 3 seconds to load the list, so I am looking to cache the resultset using Caffeine. To cache each item individually, I am following the below approach, but it doesn't seem to work.

public List<Item> getAllItemsOnStartUp() {
            allItemsList = repo.getAllItems();
            for (Item item : allItemsList) {
                staticItem = item;
                getItem(item.getItmsId());
            }
        
        return allItemsList;
    }

    @CachePut(value = "allItems", key = "#itmsId")
    public Item getItem(BigInteger itmsId) {
        return item;
    }

@Override
@Cacheable(value = "allItems")
    public List<Item> getAllItems() {
        
        allItemsList = repo.getAllItems();
        return allItemsList;
    }

@Override
    @Transactional
    @CachePut(value = "allItems", key="#a0.itmsId")
    public Item insertItem(Item item) {
      Item savedItem = rRepo.save(item);
      return savedItem;
    }

When the server starts up, getAllItemsOnStartUp() must run and populate cache. the app calls getAllItems() to retrieve the list, it is expected to use cache here but every time the app gets data from DB which takes 3 seconds.

  • I think it is because Spring AOP is proxy-based so it doesn’t intercept the local method call (see related [question](https://stackoverflow.com/questions/13564627/spring-aop-not-working-for-method-call-inside-another-method)). – Ben Manes May 10 '21 at 15:17

1 Answers1

0

I saw this post today but I will answer anyway. Maybe it can help others.

Spring-boot uses by default CaffeineCache to cache service calls and it's very simple to use it. You just have to annotate your service method using @Cacheable. The example below caches the user permissions in a cache named "sso-users-cache" (because I don't want to call the service to check user's permissions all the time), creating an entry using company name (the system is multi-company) and userId, unless the method returns an error (remember: if you don't have a clausule unless, you can cache even an error of your service).

    @Cacheable(cacheNames = ["sso-users-cache"], key = "#company.concat('-sso-user-').concat(#userId)", unless = "#result instanceof T(com.github.michaelbull.result.Err)")
fun fetchActionsByOrganizationAndUser(company: String, userId: String): Result<Set<String>, String> {
    val response = Unirest
        .get(getUserPermitionsUrl(company = company, userId = userId))
        .header("Content-Type", "application/json")
        .asString()
        .ifFailure {
            logger.error("SSO Error: Get user permissions failed: context: $company : userId: $userId")
        }

    return if(response.isSuccess) {
        Ok(serializeUtil.asCollection(response.body, String::class.java).toSet())
    } else {
        Err("SSO Error: Get user permissions failed: context: $company : userId: $userId\"")
    }
}

The parameter cacheNames defines an entry in your cache while the key, will tell the name of the entry on cache (it's used because users have different permissions and they need different entries inside the cache.

The parameter unless tells the cache to not cache if my method returns an error (very important!)

About update the cache informations, it's not necessary. What you need is to invalidate the cache information in case user's permission changes. For exemple, the method below add a new permission for the user and I need to invalidate (clear) the cache I have for the specific user:

@CacheEvict(cacheNames = ["sso-user-actions-cache"], key = "#company.concat('-user-actions-').concat(#userId)")
fun addPermissionToUser(company: String, userId: String, permission: String) {
    Unirest
        .put(addUserPermitionsUrl(company = company, userId = userId, permission = permission))
        .header("Content-Type", "application/json")
        .asEmpty()
}

Again: it's important to use the property key because I want to clear only the entry for a specific user!!! Otherwise, you will clean the whole permission cache (for all users)

After the @CacheEvict, the next call to check user's permission, spring will realize that the user doesn't have an entry on cache and will call the service and cache the return. That's it.

thiagotrss
  • 129
  • 4
  • P.S.: if you want to clear all entries of a specific cache, is necessary add allEntries=true on @CacheEvict – thiagotrss Sep 30 '22 at 13:04