Close

Spring Cache Key Generation

[Last Updated: Mar 20, 2018]

Spring Cache is nothing but a store of key-value pairs, where values are the ones returned from @Cacheable methods, whereas, for keys there has to be some strategy to generate them. By default Spring uses a simple key generation based on the following algorithm:

  • If @Cacheable method has no arguments then SimpleKey.EMPTY is used as key.

  • If only one argument is used, then the argument instance is used as key.

  • If more than one argument is used, then an instance of SimpleKey composed of all arguments is used as key.

Note that to avoid key collisions, SimpleKey class overrides hashCode() and equals() methods and compares all parameters of the target method as shown in the following snippets:

public class SimpleKey implements Serializable {
 ....
	public SimpleKey(Object... elements) {
        .....
	    this.hashCode = Arrays.deepHashCode(this.params);
	}

	@Override
	public boolean equals(Object obj) {
		return (this == obj || (obj instanceof SimpleKey
				&& Arrays.deepEquals(this.params, ((SimpleKey) obj).params)));
	}

	@Override
	public final int hashCode() {
		return this.hashCode;
	}
   ......
}

Example

In this example, we will understand the default key generation. We will also understand why we may need custom keys in some cases and how to generate them.

Java Config

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

ConcurrentMapCacheManager is another Spring provided CacheManager implementation that lazily builds ConcurrentMapCache instances for each request.

A service bean

To understand default cache generation (as stated above), we are going to create a no arg, one arg and multiple args @Cacheable methods:

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

  @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);
  }
}

The main class

public class ExampleMain {

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

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

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

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

      System.out.println("-- printing native cache --");
      CacheManager cm = context.getBean(CacheManager.class);
      Cache cache = cm.getCache("employee-cache");
      System.out.println(cache.getNativeCache());
  }
}
-- getting dept list --
getDepartments() invoked
depts returned: [Ljava.lang.String;@7fa98a66
-- getting employee by id --
getEmployeeById() invoked
employee returned: com.logicbig.example.Employee@15ff3e9e
-- getting employee by name and dept --
getEmployeeByNameAndDept() invoked
employee returned: com.logicbig.example.Employee@5fdcaa40
-- printing native cache --
{SimpleKey []=[Ljava.lang.String;@7fa98a66, 10=com.logicbig.example.Employee@15ff3e9e, SimpleKey [Linda,Admin]=com.logicbig.example.Employee@5fdcaa40}

As seen in above native cache output, the key generation is according the rules we stated in the beginning of this tutorial.

Generating custom keys

What will happen if we create multiple @Cacheable methods having same argument types? Let's see that by creating two no-args methods:

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

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

  @Cacheable
  public Employee[] getAllEmployees() {
      System.out.println("getAllEmployees() invoked");
      return new Employee[]{new Employee(30, "Joe", "Account")};
  }
}
public class ExampleMain2 {

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

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

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

      CacheManager cm = context.getBean(CacheManager.class);
      Cache cache = cm.getCache("employee-cache");
      System.out.println(cache.getNativeCache());
  }
}
-- getting dept list --
getDepartments() invoked
depts returned: [Ljava.lang.String;@441772e
-- getting all employee list --
java.lang.ClassCastException: [Ljava.lang.String; cannot be cast to [Lcom.logicbig.example.Employee;
at com.logicbig.example.EmployeeService2$$EnhancerBySpringCGLIB$$c9867eb7.getAllEmployees(<generated>)
at com.logicbig.example.ExampleMain2.main(ExampleMain2.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.logicbig.invoker.MethodCaller.main(MethodCaller.java:31)

As seen in above output, Spring attempts to retrieved the same cache value of department list (String[]) for the second method. That's because both methods map to the same key value of SimpleKey.EMPTY. To fix this issue we can define a custom key for the second method. @Cacheable method has an element key for that purpose:

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

  @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")};
  }
}

@Cacheable#key allows to use Spring Expression Language (SpEL) for computing the key dynamically. Check out this list to understand what expression metadata can be used.

In above example #root.methodName refers to the method which is being invoked.

Let's run ExampleMain2 again:

-- getting dept list --
getDepartments() invoked
depts returned: [Ljava.lang.String;@7fa98a66
-- getting all employee list --
getAllEmployees() invoked
all employees returned: [Lcom.logicbig.example.Employee;@6cf0e0ba
{SimpleKey []=[Ljava.lang.String;@7fa98a66, getAllEmployees=[Lcom.logicbig.example.Employee;@6cf0e0ba}

Also check out this to understand more scenarios where defining custom key generation is desirable.

Example Project

Dependencies and Technologies Used:

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

Spring Cache Key Generation Examples Select All Download
  • spring-cache-key-generation-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • EmployeeService2.java

    See Also