Close

Spring Cache Evict

[Last Updated: Mar 20, 2018]

Spring Cache abstraction also supports eviction of cache. This process is useful for removing stale or unused data from the cache. @CacheEvict is used on methods or on class level to perform cache eviction. Such methods act as triggers for removing data from the cache.

Just like @Cacheable, the annotation @CacheEvict provides similar configuration elements to declare the eviction. This annotation also follows the same key generation to recognize the target cache to be removed. Let's understand that with examples.

Examples

Java Config

@Configuration
@ComponentScan
@EnableCaching
public class AppConfig {
  @Bean
  public CacheManager cacheManager() {
      ConcurrentMapCacheManager cacheManager =
              new ConcurrentMapCacheManager("employee-cache");//vararg constructor
      return cacheManager;
  }
}

A service bean

@Service
@CacheConfig(cacheNames = "employee-cache")
public class EmployeeService {

  @Cacheable
  public Employee getEmployeeById(int id) {
      System.out.println("getEmployeeById() invoked");
      return new Employee(id, "Adam", "IT");
  }

  @CacheEvict
  public void clearEmployeeById(int id) {
      System.out.println("clearEmployeeById() invoked for id=" + id);
  }
}

As seen above getEmployeeId(id) is used to cache the returned value with key equals id value. That means to remove the same key in the native cache map, we need to specify same attribute (id) on the evict method.

The main class

public class ExampleMain {

  public static void main(String[] args) {
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
      EmployeeService service = context.getBean(EmployeeService.class);
      System.out.println("-- getting employee by id --");
      Employee employee = service.getEmployeeById(10);
      System.out.println("employee returned: "+employee);

      Util.printNativeCache(context);
      System.out.println("-- clearing employee cache by id --");
      service.clearEmployeeById(10);
      Util.printNativeCache(context);

      System.out.println("-- getting employee by same id --");
      employee = service.getEmployeeById(10);
      System.out.println("employee returned: "+employee);
      Util.printNativeCache(context);
  }
}
-- getting employee by id --
getEmployeeById() invoked
employee returned: com.logicbig.example.Employee@2e005c4b
-- native cache --
{10=com.logicbig.example.Employee@2e005c4b}
-- clearing employee cache by id --
clearEmployeeById() invoked for id=10
-- native cache --
{}
-- getting employee by same id --
getEmployeeById() invoked
employee returned: com.logicbig.example.Employee@4567f35d
-- native cache --
{10=com.logicbig.example.Employee@4567f35d}

As seen in above native cache output, the cache was removed by 'id' value after we called the method clearEmployeeById().

Preforming eviction and re-caching at the same time.

For that we have to use @EvictCache and @Cacheable annotations together:

@Service
@CacheConfig(cacheNames = "employee-cache")
public class EmployeeService2 {

  @Cacheable
  public Employee getEmployeeById(int id) {
      System.out.println("getEmployeeById() invoked");
      return new Employee(id, "Adam", "IT");
  }

  @CacheEvict(beforeInvocation = true)
  @Cacheable
  public Employee getRefreshedEmployeeById(int id) {
      System.out.println("getRefreshedEmployeeById() invoked");
      return new Employee(id, "Adam", "Admin");
  }
}

By default, @CacheEvict will cause the target cache to be removed after method is returned. In above case, we want it to be removed before method is invoked so we used beforeInvocation=true. This allows new value to be cached via @Cacheable after removing the cache, not before that.

public class ExampleMain2 {

  public static void main(String[] args) {
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
      EmployeeService2 service = context.getBean(EmployeeService2.class);
      System.out.println("-- getting employee by id --");
      Employee employee = service.getEmployeeById(10);
      System.out.println("employee returned: "+employee);
      Util.printNativeCache(context);

      System.out.println("-- getting refreshed employee by same id --");
      Employee employee2 = service.getRefreshedEmployeeById(10);
      System.out.println("employee returned: "+employee2);
      Util.printNativeCache(context);
  }
}
-- getting employee by id --
getEmployeeById() invoked
employee returned: com.logicbig.example.Employee@2e005c4b
-- native cache --
{10=com.logicbig.example.Employee@2e005c4b}
-- getting refreshed employee by same id --
getRefreshedEmployeeById() invoked
employee returned: com.logicbig.example.Employee@4567f35d
-- native cache --
{10=com.logicbig.example.Employee@4567f35d}

Removing all cache entries at the same time

For that we have to use @CacheEvict#allEntries=true (the default is false):

@Service
@CacheConfig(cacheNames = "employee-cache")
public class EmployeeService3 {

  @Cacheable
  public String[] getDepartments() {
      System.out.println("getDepartments() invoked");
      return new String[]{"IT", "Admin", "Account"};
  }

  @Cacheable
  public Employee getEmployeeById(int id) {
      System.out.println("getEmployeeById() invoked");
      return new Employee(id, "Adam", "IT");
  }

  @Cacheable
  public Employee getEmployeeByNameAndDept(String name, String dept) {
      System.out.println("getEmployeeByNameAndDept() invoked");
      return new Employee(20, name, dept);
  }

  @CacheEvict(allEntries = true)
  public void clearAllCache(){
      System.out.println("clearAllCache() Invoked");
  }
}
public class ExampleMain3 {

  public static void main(String[] args) {
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
      EmployeeService3 service = context.getBean(EmployeeService3.class);

      System.out.println("-- getting dept list --");
      String[] depts = service.getDepartments();
      System.out.println("depts returned: "+depts);

      System.out.println("-- getting employee by id --");
      Employee employee = service.getEmployeeById(10);
      System.out.println("employee returned: "+employee);

      System.out.println("-- getting employee by name and dept --");
      Employee employee2 = service.getEmployeeByNameAndDept("Linda", "Admin");
      System.out.println("employee returned: "+employee2);

      Util.printNativeCache(context);

      System.out.println("-- clearing all cache --");
      service.clearAllCache();

      Util.printNativeCache(context);
  }
}
-- getting dept list --
getDepartments() invoked
depts returned: [Ljava.lang.String;@5ffead27
-- getting employee by id --
getEmployeeById() invoked
employee returned: com.logicbig.example.Employee@6356695f
-- getting employee by name and dept --
getEmployeeByNameAndDept() invoked
employee returned: com.logicbig.example.Employee@4f18837a
-- native cache --
{SimpleKey []=[Ljava.lang.String;@5ffead27, 10=com.logicbig.example.Employee@6356695f, SimpleKey [Linda,Admin]=com.logicbig.example.Employee@4f18837a}
-- clearing all cache --
clearAllCache() Invoked
-- native cache --
{}

Removing cache by specifying key

As we saw in the last tutorial, there might be scenarios where we need to use @Cacheable#key to resolve the key collision. In that case, to evict the intended cache, we have to use @CacheEvict#key as well:

@Service
@CacheConfig(cacheNames = "employee-cache")
public class EmployeeService4 {

  @Cacheable
  public String[] getDepartments() {
      System.out.println("getDepartments() invoked");
      return new String[]{"IT", "Admin", "Account"};
  }

  @Cacheable(key = "#root.methodName")
  public Employee[] getAllEmployees() {
      System.out.println("getAllEmployees() invoked");
      return new Employee[]{new Employee(30, "Joe", "Account")};
  }

  @CacheEvict(key = "'getAllEmployees'")
  public void clearAllEmployeesCache(){
      System.out.println("clearAllEmployeesCache() invoked");

  }
}
public class ExampleMain4 {

  public static void main(String[] args) {
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
      EmployeeService4 service = context.getBean(EmployeeService4.class);

      System.out.println("-- getting dept list --");
      String[] depts = service.getDepartments();
      System.out.println("depts returned: " + depts);

      System.out.println("-- getting all employee list --");
      Employee[] allEmployees = service.getAllEmployees();
      System.out.println("all employees returned: " + allEmployees);

      Util.printNativeCache(context);
      System.out.println("-- clearing all employees cache --");
      service.clearAllEmployeesCache();
      Util.printNativeCache(context);
  }
}
-- getting dept list --
getDepartments() invoked
depts returned: [Ljava.lang.String;@5ffead27
-- getting all employee list --
getAllEmployees() invoked
all employees returned: [Lcom.logicbig.example.Employee;@3023df74
-- native cache --
{SimpleKey []=[Ljava.lang.String;@5ffead27, getAllEmployees=[Lcom.logicbig.example.Employee;@3023df74}
-- clearing all employees cache --
clearAllEmployeesCache() invoked
-- native cache --
{SimpleKey []=[Ljava.lang.String;@5ffead27}

In above example, if we don't specify @CacheEvict#key on the method clearAllEmployeesCache() then the cache populated by getDepartments() will be deleted because of the matching args (no arg in both getDepartments() and clearAllCache() methods)

Example Project

Dependencies and Technologies Used:

  • spring-context 5.0.4.RELEASE: Spring Context.
  • JDK 1.8
  • Maven 3.3.9

Spring Cache Eviction Examples Select All Download
  • spring-cache-evict-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • EmployeeService.java

    See Also