Friday, 27 September 2019

Spring cache: Create Custom key generator

Cache is a map kind of data structure, where it stores the data in the form of <key, value> pairs.

In this post, I am going to show you how to create custom key generator and use it while storing the data.

Step 1: Define a key generator.

We can get a key generator by implementing KeyGeneraotr interface. As you see below snippet, I given the ‘myEmployeeKeyGenerator’ as key generator name.
@Component("myEmployeeKeyGenerator")
public class EmployeeKeyGenerator implements KeyGenerator {

 @Override
 public Object generate(Object target, Method method, Object... params) {
  StringBuilder sb = new StringBuilder();

  if (target != null) {
   sb.append(target.getClass().getSimpleName()).append("-");
  }

  if (method != null) {
   sb.append(method.getName());
  }

  if (params != null) {
   for (Object param : params) {
    sb.append("-").append(param.getClass().getSimpleName()).append(":").append(param);
   }
  }
  return sb.toString();
 }

}

Step 2: Define a cache that is used to store the employee information.    
@Configuration
@EnableCaching
public class AppConfig {
 @Bean
 public CacheManager cacheManager() {
  List<ConcurrentMapCache> caches = Arrays.asList(new ConcurrentMapCache("myEmployeeCache"));
  SimpleCacheManager cacheManager = new SimpleCacheManager();
  cacheManager.setCaches(caches);
  return cacheManager;
 }
}

‘myEmployeeCache’ is the concurrent map cache.

@EnableCaching enables Spring's annotation-driven cache management capability

Step 3: You can use the cache key generator ‘myEmployeeKeyGenerator ‘ and cache ‘myEmployeeCache’ to store the information.

@Service
@CacheConfig(cacheNames = "myEmployeeCache", keyGenerator = "myEmployeeKeyGenerator")
public class EmployeeService {

 @Cacheable
 public Employee getEmployeeById(int id) {
  System.out.println("getEmployeeById() is called");

  for (Employee emp : emps) {
   if (id == emp.getId()) {
    return emp;
   }
  }
  return null;
 }

 ....
 ....
}

@CacheConfig(cacheNames = "myEmployeeCache", keyGenerator = "myEmployeeKeyGenerator")
It provides a mechanism for sharing common cache-related settings at the class level. All the @Cacheable method results of the service class are stored in ‘myEmployeeCache’ using the key generator ‘myEmployeeKeyGenerator’.

When you call the method ‘getEmployeeById’, if the result is already available in cache, then it is returned from cache, else actual method gets executed and result is stored into cache.

Find the below working application.

Employee.java    
package com.sample.app.model;

public class Employee {
 private int id;
 private String firstName;
 private String lastName;

 public Employee(int id, String firstName, String lastName) {
  this.id = id;
  this.firstName = firstName;
  this.lastName = lastName;
 }

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

 @Override
 public String toString() {
  return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
 }

}

EmployeeKeyGenerator.java
package com.sample.app.generator;

import java.lang.reflect.Method;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;

@Component("myEmployeeKeyGenerator")
public class EmployeeKeyGenerator implements KeyGenerator {

 @Override
 public Object generate(Object target, Method method, Object... params) {
  StringBuilder sb = new StringBuilder();

  if (target != null) {
   sb.append(target.getClass().getSimpleName()).append("-");
  }

  if (method != null) {
   sb.append(method.getName());
  }

  if (params != null) {
   for (Object param : params) {
    sb.append("-").append(param.getClass().getSimpleName()).append(":").append(param);
   }
  }
  return sb.toString();
 }

}

EmployeeService.java
package com.sample.app.service;

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import com.sample.app.model.Employee;
import java.util.*;

@Service
@CacheConfig(cacheNames = "myEmployeeCache", keyGenerator = "myEmployeeKeyGenerator")
public class EmployeeService {

 private static List<Employee> emps = new ArrayList<>();

 static {
  emps.add(new Employee(1, "Ram", "Kota"));
  emps.add(new Employee(2, "Raj", "Majety"));
  emps.add(new Employee(3, "PTR", "Navakotla"));
  emps.add(new Employee(4, "Krishna", "Boppana"));
 }

 @Cacheable
 public Employee getEmployeeById(int id) {
  System.out.println("getEmployeeById() is called");

  for (Employee emp : emps) {
   if (id == emp.getId()) {
    return emp;
   }
  }
  return null;
 }

 @Cacheable
 public Employee getEmployeeByFirstName(String name) {
  System.out.println("getEmployeeByFirstName() invoked");

  for (Employee emp : emps) {
   if (name.equals(emp.getFirstName())) {
    return emp;
   }
  }
  return null;
 }
}

AppConfig.java
package com.sample.app.config;

import java.util.Arrays;
import java.util.List;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class AppConfig {
 @Bean
 public CacheManager cacheManager() {
  List<ConcurrentMapCache> caches = Arrays.asList(new ConcurrentMapCache("myEmployeeCache"));
  SimpleCacheManager cacheManager = new SimpleCacheManager();
  cacheManager.setCaches(caches);
  return cacheManager;
 }
}

App.java
package com.sample.app;

import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;

import com.sample.app.model.Employee;
import com.sample.app.service.EmployeeService;

@SpringBootApplication
public class App {

 @Autowired
 private EmployeeService empService;

 @Autowired
 private CacheManager cacheManager;

 public static void main(String[] args) {
  SpringApplication.run(App.class, args);
 }

 @Bean
 public CommandLineRunner demo() {
  return (args) -> {
   printNativeCache();
   
   Employee emp = empService.getEmployeeById(1);
   System.out.println(emp);
   emp = empService.getEmployeeByFirstName("Krishna");
   System.out.println(emp);

   printNativeCache();
   
   emp = empService.getEmployeeById(1);
   System.out.println(emp);
   emp = empService.getEmployeeByFirstName("Krishna");
   System.out.println(emp);
   
   emp = empService.getEmployeeById(1);
   System.out.println(emp);
   emp = empService.getEmployeeByFirstName("Krishna");
   System.out.println(emp);
  };
 }

 public void printNativeCache() {
  System.out.println("\n**************************************");
  Cache cache = cacheManager.getCache("myEmployeeCache");
  System.out.println("-- native cache --");
  Map<String, Object> map = (Map<String, Object>) cache.getNativeCache();
  map.forEach((key, value) -> {
   System.out.println(key + " = " + value);
  });
  System.out.println("**************************************\n");
 }
}

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.sample.app</groupId>
 <artifactId>springCache</artifactId>
 <version>1</version>

 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.1.6.RELEASE</version>
 </parent>

 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 </properties>

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter</artifactId>
  </dependency>

 </dependencies>
</project>

Total project structure looks like below.


Run App.java, you can see below messages in console.
**************************************
-- native cache --
**************************************

getEmployeeById() is called
Employee [id=1, firstName=Ram, lastName=Kota]
getEmployeeByFirstName() invoked
Employee [id=4, firstName=Krishna, lastName=Boppana]

**************************************
-- native cache --
EmployeeService-getEmployeeByFirstName-String:Krishna = Employee [id=4, firstName=Krishna, lastName=Boppana]
EmployeeService-getEmployeeById-Integer:1 = Employee [id=1, firstName=Ram, lastName=Kota]
**************************************

Employee [id=1, firstName=Ram, lastName=Kota]
Employee [id=4, firstName=Krishna, lastName=Boppana]
Employee [id=1, firstName=Ram, lastName=Kota]

You can download complete working application from this link.


Previous                                                    Next                                                    Home

No comments:

Post a Comment