Wednesday 28 April 2021

Spring cache and caffeine hello world application

Caffeine cache is a high-performance caching library for Java. In this post, I am going to explain how to integrate caffeine cache in spring boot application.

 

Step 1: Define cache key generator.

@Component("myCacheKeyGenerator")
public class CacheKeyGenerator 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 cache config class by extending CachingConfigurerSupport class.

 

CacheConfig.java

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

	@Override
	public KeyGenerator keyGenerator() {
		return new CacheKeyGenerator();
	}
}


Step 3: Use this cache on the methods that you are intrested to cache the result.

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

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

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

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

}


Find the below working application.

 

Step 1: Create new maven project ‘caffeine-hello-world’.

 

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.sample.app</groupId>
	<artifactId>caffeine-hello-world</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.

 

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


OrganizaitonService.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
	public Organization getById(int id) {
		// System.out.println("getById() called");
		for (Organization org : orgs) {
			if (id == org.getId()) {
				return org;
			}
		}

		return null;
	}

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

		return null;
	}

}



Step 5: Define Cache ket generator.

 

CacheKeyGenerator.java


package com.sample.app.generator;

import java.lang.reflect.Method;

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

@Component("myCacheKeyGenerator")
public class CacheKeyGenerator 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 6: Define CacheConfig class.

 

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.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.sample.app.generator.CacheKeyGenerator;

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

	@Override
	public KeyGenerator keyGenerator() {
		return new CacheKeyGenerator();
	}
}


Step 7: Define main application class.

 

App.java

package com.sample.app;

import java.util.Collection;
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);
	}

	@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
**************************************


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

For the cache : myOrgCache
OrganizationService-getById-Integer:3 -> Organization [id=3, name=GHI Corp, description=Hello GHI Corp]
OrganizationService-getByName-String:GHI Corp -> Organization [id=3, name=GHI Corp, description=Hello GHI Corp]

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


Sleeping for 5 seconds

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

For the cache : myOrgCache
OrganizationService-getById-Integer:3 -> Organization [id=3, name=GHI Corp, description=Hello GHI Corp]
OrganizationService-getByName-String:GHI Corp -> Organization [id=3, name=GHI Corp, description=Hello GHI Corp]

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


Sleeping for 5 seconds

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

For the cache : myOrgCache

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


You can download complete working application from below link.

https://github.com/harikrishna553/springboot/tree/master/cache/caffeine-hello-world


Previous                                                    Next                                                    Home

No comments:

Post a Comment