Caching in Spring RESTful Service: Part 2 – Cache Eviction

Caching in Spring RESTful Service: Part 2 – Cache Eviction

0 Comments

Last Updated on October 21, 2024 by jt

In my previous post Caching in Spring RESTful Service: Part 1, I discussed how to implement caching for queries on products.

Now, consider a scenario where you need to delete or update a product.  The cache should be aware of these operations. Or else, the cache will return old product information.

To address such problems, we have to evict the cache whenever a user updates or deletes a product.

In this post, I will explain how to implement cache eviction in a Spring Boot RESTful API.

Implementing Cache Eviction for Product Deletion

I will continue with the Product REST service discussed in Part 1. The code of the service is available in Git.

To recall, the Product REST service has a Product domain entity, a Spring Data JPA repository, named ProductRepository, a service ProductService with a ProductServiceImpl implementation class.

The REST service also has a controller ProductController with endpoints to add a product and retrieve all products.

Let’s start with the  ProductServiceImpl class by adding a service method deleteProductById(int id)to delete a product corresponding to its id.

Every time I delete a product, I want the entire content of the cache to be deleted as well.

This is the code for it.

@Caching(evict = {@CacheEvict(value = "allproductcache", allEntries = true),
                  @CacheEvict(value = "productcache", key = "#product.id")
         })

@Override
public Product deleteProductById(int id) {
        Product product = null;
        Optional optional = productRepository.findById(id);
        if (optional.isPresent()) {
               product = productRepository.findById(id).get();
               productRepository.deleteById(id);
            }
        System.out.println("Product deleted in database\n ");
        return product;
}

In the preceding code, Line 1-Line 2 marks the class with the @CacheEvict annotation. Doing so will evict the previously loaded cache of master data. Here the key-value allEntires = true removes all entries of the cache. Also, the product.id column of "productcache" is removed.

When you need to clear an entire cache region this option comes in very handy rather than evicting each entry.

The test code is this.

@Test
void givenProductToDeleteThenShouldEvictCache() {

      productService.addProduct(product1);
      productService.addProduct(product2);

      productService.deleteProductById(1);
      productService.getAllProducts();
      productService.getAllProducts();
      productService.getAllProducts();
      productService.getAllProducts();

      verify(productRepository,times(1)).findAll();
}

The test case adds a product and then deletes a product with id 1. Then it makes four calls to retrieve all the products.

Line 13 has a verify method. It checks that the product is loaded from the database only once after the deletion method. Post that, the cache returns the products.

The output on running the test is this.

cache output for deletion

Implementing Cache Eviction for Updating a Product

Let’s,  add a service method updateProduct(Product product) to update a product and ensure that cache eviction happens as expected.

@CachePut(key = "#product.id")
@Override
public Product updateProduct(Product product){
      Product updateProduct = null;
      Optional optional = productRepository.findById(product.getId());
           if (optional.isPresent()){
           Product getProduct = productRepository.findById(product.getId()).get();
           getProduct.setPName(product.getPName());
           getProduct.setPrice(product.getPrice());
           updateProduct = addProduct(getProduct);
           }
      System.out.println("Product updated\n");
      return updateProduct;
  }

In the preceding code, @CachePut is used. It is a method level annotation. When you want to update the cache without interfering with the method execution, use this annotation.

The test code is this.

@Test
void givenProductToUpdateThenShouldEvictCache() {

     productService.addProduct(product1);
     productService.addProduct(product2);

      productService.updateProduct(product1);
      product1.setPName("Sample Product");

      productService.getAllProducts();
      productService.getAllProducts();
      productService.getAllProducts();
      productService.getAllProducts();

      verify(productRepository,times(1)).findAll();
}

In the preceding code Line 3- Line 4 adds two Product objects: product1 and product2. Line 8 updates the name of the product1. Then the code makes four calls to retrieve all the products.

At Line 15, there is a verify method. It checks that only once the product is loaded from the database after it gets updated. After that, the cache keeps returning the product without querying the database.

The output of the test is this.

output for updated cache

Summary

In this post, I have only shown the basic caching mechanism. Spring provides several implementations of caching. Some examples are Ehcache3, Redis,  Caffeine, Pivotal GemFire, and so on.

I have already written a post on Ehcache3 titled Using Ehcache 3 in Spring Boot.

Redis is also extensively used in enterprise applications to manage cache because of its ability to withstand failures and provide uninterrupted services.

Redis is blazing fast and favors high performance over consistency.

On the other hand, if you looking for high scalability with high concurrency then you can go for Pivotal GemFire.

You can find the source code of this post on Github.

For in-depth knowledge of the Spring Framework, check my Udemy Best Seller Course Spring Framework 5: Beginner to Guru.

About SFG Contributor

Staff writer account for Spring Framework Guru

    You May Also Like

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    This site uses Akismet to reduce spam. Learn how your comment data is processed.