Thursday, 30 June 2022

Spring boot: ehcache: Receive cache events by implementing CacheEventListener

In this post, I am going to explain how to get the cache events by implementing CacheEventListener.

 

Supported event types

org.ehcache.event.EventType enum define below event types.

 

a.   EVICTED : Represents a cache entry being evicted

b.   EXPIRED : Represents a cache entry being expired

c.    REMOVED : Represents a cache entry being removed

d.   CREATED : Represents a cache entry being created

e.   UPDATED : Represents a cache entry being updated

 

Supported event firing modes

org.ehcache.event.EventFiring enum defines below event firing modes.

 

a.   ASYNCHRONOUS: Events will fire asynchronously. The mutating thread does not have to wait for firing to complete.

b.   SYNCHRONOUS: Events will fire synchronously. The mutating thread will wait for firing to complete.

 

Supported event ordering modes

org.ehcache.event.EventOrdering define below event ordering modes.

a.   UNORDERED: Events may be observed out of order.

b.   ORDERED: Ordering of events is guaranteed on a per-key basis.

 

Let’s define custom cache event listener by implementing CacheEventListener interface.

public class MyCacheEnventListener implements CacheEventListener<Object, Object> {
	private static final Logger LOGGER = LoggerFactory.getLogger(MyCacheEnventListener.class);

	public void onEvent(CacheEvent<? extends Object, ? extends Object> cacheEvent) {
		LOGGER.info("\n-----------------------------------------------");
		LOGGER.info("Event received");
		LOGGER.info("event type: {}", cacheEvent.getType());
		LOGGER.info("event key: {}", cacheEvent.getKey());
		LOGGER.info("old value: {}", cacheEvent.getOldValue());
		LOGGER.info("new value: {}", cacheEvent.getNewValue());
		LOGGER.info("-----------------------------------------------");

	}
}

 

Next configure this ‘MyCacheEnventListener’ in ehcache.xml file.

 

ehcache.xml

 

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://www.ehcache.org/v3"
	xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
	xsi:schemaLocation="
            http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
            http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">

	<cache alias="myEmployeeCache">
		<key-type>java.lang.Object</key-type>
		<value-type>java.lang.Object</value-type>
		<expiry>
			<ttl unit="seconds">10</ttl>
		</expiry>

		<listeners>
			<listener>
				<class>com.sample.app.listeners.MyCacheEnventListener</class>
				<event-firing-mode>SYNCHRONOUS</event-firing-mode>
				<event-ordering-mode>UNORDERED</event-ordering-mode>
				<events-to-fire-on>CREATED</events-to-fire-on>
				<events-to-fire-on>EXPIRED</events-to-fire-on>
			</listener>
		</listeners>

		<resources>
			<heap unit="entries">2</heap>
			<offheap unit="MB">10</offheap>
		</resources>


	</cache>


</config>

 

As you observe the definition of xml file, I set event-firing-mode to SYNCHRONOUS for the demo purpose, you can set this to 'ASYNCHRONOUS' in production.

 

Specify ehcache.xml file location in application.yml file.

spring:
  cache:
    jcache:
      config: classpath:ehcache.xml 

 

Follow below step-by-step procedure to build complete working application.

 

Step 1: Create new maven project ‘spring-cache-event-listener’.

 

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>spring-cache-event-listener</artifactId>
	<version>1</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.6</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>org.ehcache</groupId>
			<artifactId>ehcache</artifactId>
		</dependency>

		<dependency>
			<groupId>javax.cache</groupId>
			<artifactId>cache-api</artifactId>
		</dependency>

	</dependencies>
</project>

Step 3: Create ehcache.xml file under src/main/resources folder.

 

ehcache.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://www.ehcache.org/v3"
	xmlns:jsr107="http://www.ehcache.org/v3/jsr107"
	xsi:schemaLocation="
            http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
            http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">

	<cache alias="myEmployeeCache">
		<key-type>java.lang.Object</key-type>
		<value-type>java.lang.Object</value-type>
		<expiry>
			<ttl unit="seconds">10</ttl>
		</expiry>

		<listeners>
			<listener>
				<class>com.sample.app.listeners.MyCacheEnventListener</class>
				<event-firing-mode>SYNCHRONOUS</event-firing-mode>
				<event-ordering-mode>ORDERED</event-ordering-mode>
				<events-to-fire-on>EVICTED</events-to-fire-on>
				<events-to-fire-on>EXPIRED</events-to-fire-on>
				<events-to-fire-on>REMOVED</events-to-fire-on>
				<events-to-fire-on>CREATED</events-to-fire-on>
				<events-to-fire-on>UPDATED</events-to-fire-on>

			</listener>
		</listeners>

		<resources>
			<heap unit="entries">2</heap>
			<offheap unit="MB">10</offheap>
		</resources>


	</cache>


</config>

Step 4: Create application.yml file under src/main/resources folder.

 

application.yml

spring:
  cache:
    jcache:
      config: classpath:ehcache.xml

Step 5: Create Employee model class.

 

Employee.java

package com.sample.app.model;

import java.io.Serializable;

public class Employee implements Serializable{
	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 6: Define EmployeeService class.

 

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: Getting the informaiton from internal list");
		
		for (Employee emp : emps) {
			if (id == emp.getId()) {
				return emp;
			}
		}
		return null;
	}

	@Cacheable(key = "{#name}")
	public Employee getEmployeeByFirstName(String name) {
		System.out.println("getEmployeeByFirstName: Getting the informaiton from internal list");
		
		for (Employee emp : emps) {
			if (name.equals(emp.getFirstName())) {
				return emp;
			}
		}
		return null;
	}
}

Step 7: Define MyCacheEnventListener class.

MyCacheEnventListener.java

package com.sample.app.listeners;

import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyCacheEnventListener implements CacheEventListener<Object, Object> {
	private static final Logger LOGGER = LoggerFactory.getLogger(MyCacheEnventListener.class);

	public void onEvent(CacheEvent<? extends Object, ? extends Object> cacheEvent) {
		LOGGER.info("\n-----------------------------------------------");
		LOGGER.info("Event received");
		LOGGER.info("event type: {}", cacheEvent.getType());
		LOGGER.info("event key: {}", cacheEvent.getKey());
		LOGGER.info("old value: {}", cacheEvent.getOldValue());
		LOGGER.info("new value: {}", cacheEvent.getNewValue());
		LOGGER.info("-----------------------------------------------");

	}
}

 

Step 8: Define CacheConfig class.

 

CacheConfig.java

package com.sample.app.config;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableCaching
public class CacheConfig {

} 

 

Step 9: Define main application class.

 

App.java

package com.sample.app;

import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.context.annotation.Bean;

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

@SpringBootApplication
public class App {

	private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

	@Autowired
	private EmployeeService empService;

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

	public void getEmployeeDetails() {

		System.out.println("\n");
		LOGGER.info("\nGetting employee details with id 1");
		Employee emp = empService.getEmployeeById(1);
		LOGGER.info("emp : {}", emp);

		System.out.println("\n");
		LOGGER.info("Getting employee details with name 'Krishna'");
		emp = empService.getEmployeeByFirstName("Krishna");
		LOGGER.info("emp : {}", emp);
	}

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

			getEmployeeDetails();

			LOGGER.info("Sleeping for 4 seconds");
			TimeUnit.SECONDS.sleep(4);
			getEmployeeDetails();

			LOGGER.info("Sleeping for 4 seconds");
			TimeUnit.SECONDS.sleep(4);
			getEmployeeDetails();

			LOGGER.info("Sleeping for 4 seconds");
			TimeUnit.SECONDS.sleep(4);
			getEmployeeDetails();

		};
	}
}

 

Total project structure looks like below.

 


 

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

Getting employee details with id 1
getEmployeeById: Getting the informaiton from internal list
2022-06-30 16:15:17.063  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : 
-----------------------------------------------
2022-06-30 16:15:17.064  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : Event received
2022-06-30 16:15:17.064  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event type: CREATED
2022-06-30 16:15:17.064  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event key: [1]
2022-06-30 16:15:17.064  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : old value: null
2022-06-30 16:15:17.064  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : new value: Employee [id=1, firstName=Ram, lastName=Kota]
2022-06-30 16:15:17.064  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : -----------------------------------------------
2022-06-30 16:15:17.065  INFO 82133 --- [           main] com.sample.app.App                       : emp : Employee [id=1, firstName=Ram, lastName=Kota]


2022-06-30 16:15:17.065  INFO 82133 --- [           main] com.sample.app.App                       : Getting employee details with name 'Krishna'
getEmployeeByFirstName: Getting the informaiton from internal list
2022-06-30 16:15:17.066  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : 
-----------------------------------------------
2022-06-30 16:15:17.066  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : Event received
2022-06-30 16:15:17.066  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event type: CREATED
2022-06-30 16:15:17.066  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event key: [Krishna]
2022-06-30 16:15:17.066  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : old value: null
2022-06-30 16:15:17.066  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : new value: Employee [id=4, firstName=Krishna, lastName=Boppana]
2022-06-30 16:15:17.066  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : -----------------------------------------------
2022-06-30 16:15:17.066  INFO 82133 --- [           main] com.sample.app.App                       : emp : Employee [id=4, firstName=Krishna, lastName=Boppana]
2022-06-30 16:15:17.067  INFO 82133 --- [           main] com.sample.app.App                       : Sleeping for 4 seconds


2022-06-30 16:15:21.069  INFO 82133 --- [           main] com.sample.app.App                       : 
Getting employee details with id 1
2022-06-30 16:15:21.077  INFO 82133 --- [           main] com.sample.app.App                       : emp : Employee [id=1, firstName=Ram, lastName=Kota]


2022-06-30 16:15:21.078  INFO 82133 --- [           main] com.sample.app.App                       : Getting employee details with name 'Krishna'
2022-06-30 16:15:21.078  INFO 82133 --- [           main] com.sample.app.App                       : emp : Employee [id=4, firstName=Krishna, lastName=Boppana]
2022-06-30 16:15:21.078  INFO 82133 --- [           main] com.sample.app.App                       : Sleeping for 4 seconds


2022-06-30 16:15:25.079  INFO 82133 --- [           main] com.sample.app.App                       : 
Getting employee details with id 1
2022-06-30 16:15:25.080  INFO 82133 --- [           main] com.sample.app.App                       : emp : Employee [id=1, firstName=Ram, lastName=Kota]


2022-06-30 16:15:25.080  INFO 82133 --- [           main] com.sample.app.App                       : Getting employee details with name 'Krishna'
2022-06-30 16:15:25.080  INFO 82133 --- [           main] com.sample.app.App                       : emp : Employee [id=4, firstName=Krishna, lastName=Boppana]
2022-06-30 16:15:25.080  INFO 82133 --- [           main] com.sample.app.App                       : Sleeping for 4 seconds


2022-06-30 16:15:29.082  INFO 82133 --- [           main] com.sample.app.App                       : 
Getting employee details with id 1
2022-06-30 16:15:29.085  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : 
-----------------------------------------------
2022-06-30 16:15:29.085  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : Event received
2022-06-30 16:15:29.085  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event type: EXPIRED
2022-06-30 16:15:29.085  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event key: [1]
2022-06-30 16:15:29.085  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : old value: Employee [id=1, firstName=Ram, lastName=Kota]
2022-06-30 16:15:29.085  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : new value: null
2022-06-30 16:15:29.085  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : -----------------------------------------------
getEmployeeById: Getting the informaiton from internal list
2022-06-30 16:15:29.087  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : 
-----------------------------------------------
2022-06-30 16:15:29.087  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : Event received
2022-06-30 16:15:29.087  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event type: CREATED
2022-06-30 16:15:29.087  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event key: [1]
2022-06-30 16:15:29.087  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : old value: null
2022-06-30 16:15:29.087  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : new value: Employee [id=1, firstName=Ram, lastName=Kota]
2022-06-30 16:15:29.087  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : -----------------------------------------------
2022-06-30 16:15:29.087  INFO 82133 --- [           main] com.sample.app.App                       : emp : Employee [id=1, firstName=Ram, lastName=Kota]


2022-06-30 16:15:29.087  INFO 82133 --- [           main] com.sample.app.App                       : Getting employee details with name 'Krishna'
2022-06-30 16:15:29.088  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : 
-----------------------------------------------
2022-06-30 16:15:29.088  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : Event received
2022-06-30 16:15:29.088  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event type: EXPIRED
2022-06-30 16:15:29.088  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event key: [Krishna]
2022-06-30 16:15:29.088  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : old value: Employee [id=4, firstName=Krishna, lastName=Boppana]
2022-06-30 16:15:29.088  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : new value: null
2022-06-30 16:15:29.088  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : -----------------------------------------------
getEmployeeByFirstName: Getting the informaiton from internal list
2022-06-30 16:15:29.089  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : 
-----------------------------------------------
2022-06-30 16:15:29.089  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : Event received
2022-06-30 16:15:29.089  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event type: CREATED
2022-06-30 16:15:29.089  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : event key: [Krishna]
2022-06-30 16:15:29.089  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : old value: null
2022-06-30 16:15:29.089  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : new value: Employee [id=4, firstName=Krishna, lastName=Boppana]
2022-06-30 16:15:29.089  INFO 82133 --- [e [_default_]-0] c.s.app.listeners.MyCacheEnventListener  : -----------------------------------------------
2022-06-30 16:15:29.089  INFO 82133 --- [           main] com.sample.app.App                       : emp : Employee [id=4, firstName=Krishna, lastName=Boppana]
2022-06-30 16:15:59.100  INFO 82133 --- [ionShutdownHook] org.ehcache.core.EhcacheManager          : Cache 'myEmployeeCache' removed from EhcacheManager.

 

You can download complete working application from this link.


 

 

Previous                                                 Next                                                 Home

No comments:

Post a Comment