Wednesday, 4 August 2021

Spring cache: specify cache key using method arguments

You can construct a cache key with method parameters and specify it using ‘key’ attribute of @Cacheable annotation.

 

Example1: Method with one parameter

 @Cacheable(key = "#name")
public Organization getByName(String name) {
	// System.out.println("getByName() called");
	for (Organization org : orgs) {
		if (org.getName().equals(name)) {
			return org;
		}
	}

	return null;
}

Above example use ‘name’ as key.

 

Example2: Method with multiple parameters

@Cacheable(key = "{#id, #name }")
public List<Organization> getByNameOrId(int id, String name) {
	List<Organization> myOrgs = new ArrayList<>();

	for (Organization org : orgs) {
		if (org.getName().equals(name)) {
			myOrgs.add(org);
			continue;
		}

		if (org.getId() == id) {
			myOrgs.add(org);
		}
	}

	return myOrgs;
}


Above example use the parameters id and name as key.


 

Find the below working application.

 

Step 1: Create new maven project ‘cache-key-method-params’.

 

Step 2: Update pom.xml with maven dependencies.

 

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.sample.app</groupId>
	<artifactId>cache-key-method-params</artifactId>
	<version>1</version>

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

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

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

		<dependency>
			<groupId>com.github.ben-manes.caffeine</groupId>
			<artifactId>caffeine</artifactId>
		</dependency>

	</dependencies>
</project>

 

Step 3: Define model classes.

 

Organization.java

 

package com.sample.app.model;

public class Organization {
	private int id;
	private String name;
	private String description;

	public Organization(int id, String name, String description) {
		super();
		this.id = id;
		this.name = name;
		this.description = description;
	}

	public int getId() {
		return id;
	}

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	@Override
	public String toString() {
		return "Organization [id=" + id + ", name=" + name + ", description=" + description + "]";
	}

}

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 + "]";
	}

}


Step 4: Define service classes.

 

OrganizationService.java

package com.sample.app.service;

import java.util.ArrayList;
import java.util.List;

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

import com.sample.app.model.Organization;

@Service
@CacheConfig(cacheNames = "myOrgCache")
public class OrganizationService {
	private static final List<Organization> orgs = new ArrayList<>();

	static {
		Organization org1 = new Organization(1, "ABC Corp", "Hello ABC Corp");
		Organization org2 = new Organization(2, "DEF Corp", "Hello DEF Corp");
		Organization org3 = new Organization(3, "GHI Corp", "Hello GHI Corp");
		Organization org4 = new Organization(4, "JKL Corp", "Hello JKL Corp");
		Organization org5 = new Organization(5, "MNO Corp", "Hello MNO Corp");

		orgs.add(org1);
		orgs.add(org2);
		orgs.add(org3);
		orgs.add(org4);
		orgs.add(org5);
	}

	@Cacheable(key = "#id")
	public Organization getById(int id) {
		// System.out.println("getById() called");
		for (Organization org : orgs) {
			if (id == org.getId()) {
				return org;
			}
		}

		return null;
	}

	@Cacheable(key = "#name")
	public Organization getByName(String name) {
		// System.out.println("getByName() called");
		for (Organization org : orgs) {
			if (org.getName().equals(name)) {
				return org;
			}
		}

		return null;
	}

	@Cacheable(key = "{#id, #name }")
	public List<Organization> getByNameOrId(int id, String name) {
		List<Organization> myOrgs = new ArrayList<>();

		for (Organization org : orgs) {
			if (org.getName().equals(name)) {
				myOrgs.add(org);
				continue;
			}

			if (org.getId() == id) {
				myOrgs.add(org);
			}
		}

		return myOrgs;
	}

}


EmployeeService.java

package com.sample.app.service;

import java.util.ArrayList;
import java.util.List;

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

import com.sample.app.model.Employee;

@Service
@CacheConfig(cacheNames = "myEmployeeCache")
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(key = "#id")
	public Employee getEmployeeById(int id) {
		// System.out.println("getEmployeeById() is called");

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

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

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


Step 5: Define cache configuration.

 

CacheConfig.java

package com.sample.app.config;

import java.util.concurrent.TimeUnit;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.github.benmanes.caffeine.cache.Caffeine;

@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
	@Bean
	@Override
	public CacheManager cacheManager() {
		CaffeineCacheManager cacheManager = new CaffeineCacheManager("myOrgCache", "myEmployeeCache");
		cacheManager.setCaffeine(caffeineCacheBuilder());
		return cacheManager;
	}

	Caffeine caffeineCacheBuilder() {
		return Caffeine.newBuilder().initialCapacity(100).maximumSize(500).expireAfterWrite(10, TimeUnit.SECONDS)
				.recordStats();
	}

}


Step 6: Define main application class.

 

App.java

package com.sample.app;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

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.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.context.annotation.Bean;

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

@SpringBootApplication
public class App {

	@Autowired
	private EmployeeService empService;

	@Autowired
	private OrganizationService orgService;

	@Autowired
	private CacheManager cacheManager;

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

	public void getEmployeeAndOrgDetails() {

		Employee emp = empService.getEmployeeById(1);
		// System.out.println(emp);
		emp = empService.getEmployeeByFirstName("Krishna");
		// System.out.println(emp);

		Organization org = orgService.getById(3);
		// System.out.println(org);
		org = orgService.getByName("GHI Corp");
		// System.out.println(org);

		List<Organization> orgs = orgService.getByNameOrId(3, "JKL Corp");
		orgs.forEach(System.out::println);
	}

	@Bean
	public CommandLineRunner demo() {
		return (args) -> {
			printNativeCache();
			getEmployeeAndOrgDetails();

			printNativeCache();
			// getEmployeeAndOrgDetails();

			System.out.println("\nSleeping for 5 seconds");
			TimeUnit.SECONDS.sleep(5);
			printNativeCache();

			System.out.println("\nSleeping for 5 seconds");
			TimeUnit.SECONDS.sleep(5);
			printNativeCache();

		};
	}

	public void printNativeCache() {
		System.out.println("\n**************************************");
		System.out.println("-- native cache --");
		Collection<String> cacheNames = cacheManager.getCacheNames();

		for (String cacheName : cacheNames) {
			System.out.println("\nFor the cache : " + cacheName);
			CaffeineCache cache = (CaffeineCache) cacheManager.getCache(cacheName);
			com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache = cache.getNativeCache();
			Map<Object, Object> map = nativeCache.asMap();

			for (Object key : map.keySet()) {
				System.out.println(key + " -> " + map.get(key));
			}

		}

		System.out.println("**************************************\n");
	}
}


Total project structure looks like below.






Run App.java, you will see below messages in console.

**************************************
-- native cache --

For the cache : myOrgCache

For the cache : myEmployeeCache
**************************************

Organization [id=3, name=GHI Corp, description=Hello GHI Corp]
Organization [id=4, name=JKL Corp, description=Hello JKL Corp]

**************************************
-- native cache --

For the cache : myOrgCache
3 -> Organization [id=3, name=GHI Corp, description=Hello GHI Corp]
[3, JKL Corp] -> [Organization [id=3, name=GHI Corp, description=Hello GHI Corp], Organization [id=4, name=JKL Corp, description=Hello JKL Corp]]
GHI Corp -> Organization [id=3, name=GHI Corp, description=Hello GHI Corp]

For the cache : myEmployeeCache
1 -> Employee [id=1, firstName=Ram, lastName=Kota]
Krishna -> Employee [id=4, firstName=Krishna, lastName=Boppana]
**************************************


Sleeping for 5 seconds

**************************************
-- native cache --

For the cache : myOrgCache
3 -> Organization [id=3, name=GHI Corp, description=Hello GHI Corp]
[3, JKL Corp] -> [Organization [id=3, name=GHI Corp, description=Hello GHI Corp], Organization [id=4, name=JKL Corp, description=Hello JKL Corp]]
GHI Corp -> Organization [id=3, name=GHI Corp, description=Hello GHI Corp]

For the cache : myEmployeeCache
1 -> Employee [id=1, firstName=Ram, lastName=Kota]
Krishna -> Employee [id=4, firstName=Krishna, lastName=Boppana]
**************************************


Sleeping for 5 seconds

**************************************
-- native cache --

For the cache : myOrgCache

For the cache : myEmployeeCache
**************************************


You can download complete working application from this link.



 

Previous                                                    Next                                                    Home

No comments:

Post a Comment