skip to Main Content

I have a method that saves cache.

@GetMapping("/orders")
@Cacheable(value = "List<Order>", key = "{#customerName+'s orders', #page, #size}")
public List<Order> findOrders(@RequestParam("customerName") String customerName,
                              @RequestParam(name = "page") int page,
                              @RequestParam(name = "size") int size) {
...

And I want to remove this cache after calling of this method.

@PostMapping("/order")
@CacheEvict(value = "*",key="{#makeOrderDTO.customerDTO.name+'s orders', '', 'regex:*'}")
public Order makeOrder(@RequestBody MakeOrderDTO makeOrderDTO) {

I have a problem that I want to remove all cached pages for customers, but I can’t remove only one type of page. So I need to remove customers with all pages sizes and numbers. Maybe there is some kind of functionality like

@PostMapping("/order")
@CacheEvict(value = "*",key="{#makeOrderDTO.customerDTO.name+'s orders', 'regex:*', 'regex:*'}")
public Order makeOrder(@RequestBody MakeOrderDTO makeOrderDTO) {

or

@PostMapping("/order")
@CacheEvict(value = "*",key="{#makeOrderDTO.customerDTO.name+'s orders', 'any', 'any'}")
public Order makeOrder(@RequestBody MakeOrderDTO makeOrderDTO) {

Thanks in advance.


2

Answers


  1. You couldn’t do this, because all cache technology uses key-value store. So if you use multiple parameters for key like this:

    key = "{#customerName+'s orders', #page, #size}"
    

    Spring creates a hash as key, for example: -172893112.

    Ref: https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/cache.html#cache-annotations-cacheable-default-key

    I recommend to you that change the cache name to use "’List’ + #customerName+’s orders’" but that is not supported using annotations at least that you use CacheResolver. See here.

    @GetMapping("/orders")
    @Cacheable(value = "List<Order>", key = "{#customerName+'s orders', #page, #size}")
    public List<Order> findOrders(@RequestParam("customerName") String customerName, @RequestParam(name = "page") int page, @RequestParam(name = "size") int size) {
      //...
      List<Order> result = //...Do the operation
      String cacheName = "List<Order>" + customerName + "s orders";
      redisTemplate
        .opsForHash()
        .put(cacheName, Objects.hash(customerName, page, size), result);
    }
    

    After that, the evict method should be:

    @PostMapping("/order")
    public Order makeOrder(@RequestBody MakeOrderDTO makeOrderDTO) {
      String cacheName = "List<Order>" + makeOrderDTO.getCustomerDTO().getName() + "s orders";
      redisTemplate
        .opsForHash()
        .keys(cacheName)
        .forEach(x -> redisTemplate.opsForHash().delete(cacheName, x));
    
      //...Do operations
    }
    

    You could check this another post: Cache evict on one of multiple keys

    Login or Signup to reply.
  2. Looking at your initial method:

    @GetMapping("/orders")
    @Cacheable(value = "List<Order>", key = "{#customerName+'s orders', #page, #size}")
    public List<Order> findOrders(@RequestParam("customerName") String customerName,
                                  @RequestParam(name = "page") int page,
                                  @RequestParam(name = "size") int size) {
    ...
    

    Guessing from the signature, the method is doing many things:

    • retrieve orders from somewhere
    • do some pagination
    • providing a REST interface

    If the pagination parameter varies, you may retrieve and cache the same order multiple times. Once making a new order, you would need to re-read the previous orders, that were not changed. That is sub optimal.

    I suggest separating the concerns of data access and caching the data and servicing the web request in two classes or layers, since the web layer sits on top of the data access layer.

    Also, always use ids and not the customer name as key, since names may have duplicates and also change (yes, it happens! Typos, marriage and company mergers).

    For caching query results I always suggest caching the query result and the data separately. E.g. in your case in the data access layer:

    public List<Integer> findOrders(int customerId);
    public Order retrieveOrder(int orderId);
    

    Use two separate caches for it.

    You can then implement the pagination in the REST layer.

    public Order makeOrder(MakeOrderDTO makeOrderDTO)
    

    When placing a new order, the query result with the order list becomes invalid. You can either add the ID, or use evict to invalidate the query result cache. However, this will not effect the cache with the actual order data.

    I left out the actual Spring caching annotations. As soon as you have sorted the interfaces, these should be simple and straight forward.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search