Spring JCache support

Introduction

We will create an example on Spring JCache Support. Spring provides seamless JCache integration. In this example, I am using EhCache as a JCache specification provider.

JCache is JSR 107 specification that provides annotations support such as @CacheResult, @CachePut, @CacheRemove and @CacheRemoveAll.

Prerequisites

Eclipse 4.12, Java 12 or 8, Gradle 5.6 or Maven 3.6.1, EhCache API, JCache API

Useful JCache Annotations

@CacheResult

Example:

@CacheResult(cacheName = "person")
findPerson(@CacheKey String id)

By marking the method with the @CacheResult annotation, we tell the container that the findPerson() method is backed by the cache entry person.

That is each time the method is called, a cache lookup is performed using as key the method parameters (in this case the id argument).

If a value is found, it will be returned and the method execution skipped.

However, if the key is not found, the method is executed as usual and its result stored in the cache so the next time the method is invoked, the result can be returned without actually executing the (expensive or slow) method.

@CachePut

Example:

@CachePut
public Person savePerson(@CacheKey String id, @CacheValue Person person)

Using the above annotation Person object is kept into a cache.

The parameter annotated with @CacheValue is required for @CachePut.

@CacheRemove and @CacheRemoveAll

Examples:

@CacheRemove
public void removePerson(Person person)
@CacheRemoveAll
public void removeAllPersons()

You can remove either one cache object at a time or all cache objects at once using the above annotations, receptively.

Related Posts:

Now I will show you an example on JCache using EhCache implementation here.

Creating Project

Create a gradle or maven based project in Eclipse. The project name is spring-jcache-support.

If you create maven based project then create standalone project.

Now enter the required fields (Group Id, Artifact Id) as shown below:

Group Id : com.roytuts
Artifact Id : spring-jcache-support

Build File

Update build.gradle or pom.xml file to include the required dependencies:

build.gradle

buildscript {
	ext {
		springBootVersion = '2.1.8.RELEASE'
	}
	
    repositories {
    	mavenLocal()
    	mavenCentral()
    }
    
    dependencies {
    	classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

sourceCompatibility = 12
targetCompatibility = 12

repositories {
	mavenLocal()
    mavenCentral()
    maven {
    	url 'http://www.terracotta.org/download/reflector/releases'
    }
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
	//implementation("org.springframework:spring-context-support:5.1.9.RELEASE")
	implementation("javax.cache:cache-api:1.1.1")
	implementation("org.ehcache:jcache:1.0.0")
	implementation("net.sf.ehcache:ehcache-ee:2.8.5")
}

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.roytuts</groupId>
	<artifactId>spring-jcache-support</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	
	<repositories>
		<repository>
			<id>terracotta-releases</id>
			<url>http://www.terracotta.org/download/reflector/releases</url>
			<releases>
				<enabled>true</enabled>
			</releases>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>
	
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<java.version>12</java.version>
		<ehcache.version>2.8.5</ehcache.version>
	</properties>
	
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.8.RELEASE</version>
	</parent>
	
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>javax.cache</groupId>
			<artifactId>cache-api</artifactId>
		</dependency>
		<!-- ehcache jars along with its jsr107 impl -->
		<dependency>
			<groupId>org.ehcache</groupId>
			<artifactId>jcache</artifactId>
			<version>1.0.0</version>
		</dependency>
		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache-ee</artifactId>
			<version>${ehcache.version}</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

EhCache Configuration

Create ehcache.xml file put under src/main/resources.

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
	monitoring="autodetect" dynamicConfig="true">
	<diskStore path="java.io.tmpdir" />
	<cache name="person" maxEntriesLocalHeap="10000"
		maxEntriesLocalDisk="1000" eternal="false" diskSpoolBufferSizeMB="20"
		timeToIdleSeconds="300" timeToLiveSeconds="600"
		memoryStoreEvictionPolicy="LFU" transactionalMode="off">
		<persistence strategy="localTempSwap" />
	</cache>
</ehcache>

Creating Model Class

Create below Person model class which will be put under cache.

package com.roytuts.spring.jcache.support.model;

import java.io.Serializable;

public class Person implements Serializable {

	private static final long serialVersionUID = 1L;

	private String id;
	private String name;

	public Person(String id, String name) {
		this.id = id;
		this.name = name;
	}

	public String getId() {
		return id;
	}

	public String getName() {
		return name;
	}

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

}

Creating Repository Class

Create below PersonRepositoryImpl class. We are not using any persistence storage here but for simplicity we are using below class.

package com.roytuts.spring.jcache.support.repository;

import javax.annotation.PostConstruct;
import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.annotation.CacheDefaults;
import javax.cache.annotation.CacheKey;
import javax.cache.annotation.CachePut;
import javax.cache.annotation.CacheRemove;
import javax.cache.annotation.CacheRemoveAll;
import javax.cache.annotation.CacheResult;
import javax.cache.annotation.CacheValue;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Repository;

import com.roytuts.spring.jcache.support.model.Person;

@Repository
@EnableCaching
@CacheConfig(cacheNames = "person")
@CacheDefaults(cacheName = "person")
public class PersonRepository {

	@Autowired
	private CacheManager cacheManager;
	private Cache<String, Person> cache;
	private static String[] names = new String[] { "Younker Patel", "Zollicoffer Robinson", "Zeh Haugen",
			"Yager Johansen", "Zickefoose Macdonald", "Yerkes Karlsson", "Yerby Gustavsson", "Zimple Svensson",
			"Youmans Stewart", "Zahn Davis", "Zenz Davis", "Zamastil Jackson", "Zamastil Gustavsson", "Zucchero Walker",
			"Zielke Martin", "Zabowski Carlsson", "Yoes Hansson", "Zuczek Smith", "Zeidler Watson", "Yingling Harris",
			"Zahn Karlsen", "Zimmermann Olsson", "Zerkey Martin", "Zatovich Andersson", "Yurky Andersson",
			"Yeary Carlsson", "Yeary Olsen", "Zabowski Olsen", "Zuber Jackson", "Zeim Nilsen" };

	@PostConstruct
	public void init() {
		cache = cacheManager.getCache("person");
		int id = 1;
		for (String name : names) {
			cache.putIfAbsent(String.valueOf(id), new Person(String.valueOf(id), name));
			id++;
		}
	}

	@CacheResult
	public Person findPerson(@CacheKey String id) {
		return (Person) cache.get(id);
	}

	@CachePut
	public Person savePerson(@CacheKey String id, @CacheValue Person person) {
		return cache.getAndPut(id, person);
	}

	@CacheRemove
	public void removePerson(Person person) {
		cache.getAndRemove(person.getId());
	}

	@CacheRemoveAll
	public void removeAllPersons() {
		cache.clear();
	}

}

Creating Config Class

We are creating below config class to create JCacheManager which will provide caching mechanism.

package com.roytuts.spring.jcache.support.config;

import javax.cache.CacheManager;

import org.ehcache.jcache.JCacheCachingProvider;
import org.ehcache.jcache.JCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class JCacheConfig {

	@Bean
	public CacheManager jCacheManager() {
		JCacheManager cacheManager = new JCacheManager(new JCacheCachingProvider(), ehcache(), null, null);
		return cacheManager;
	}

	private net.sf.ehcache.CacheManager ehcache() {
		net.sf.ehcache.CacheManager cacheManager = new net.sf.ehcache.CacheManager();
		return cacheManager;
	}

}

Creating Main Class

Create below Spring Boot main class that will run our application in command line tool.

package com.roytuts.spring.jcache.support;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.roytuts.spring.jcache.support.model.Person;
import com.roytuts.spring.jcache.support.repository.PersonRepository;

@SpringBootApplication(scanBasePackages = "com.roytuts.spring.jcache.support")
public class SpringJCacheApp implements CommandLineRunner {

	@Autowired
	private PersonRepository personRepository;

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

	@Override
	public void run(String... args) throws Exception {
		System.out.println("Fetching Person details");
		Person person = personRepository.findPerson(String.valueOf(1));
		System.out.println("Cached Person Info: " + person);
		personRepository.savePerson("1000", new Person("1000", "Soumitra Roy"));
		System.out.println("Saved Person Info in cache: " + personRepository.findPerson("1000"));
		personRepository.removePerson(new Person("1000", "Soumitra Roy"));
		System.out.println("Has Person Info removed from cache: " + (personRepository.findPerson("1000") != null));
		personRepository.removeAllPersons();
		System.out
				.println("Is there still any Person Info in the cache: " + (personRepository.findPerson("1") != null));
	}
}

Testing the Application

Now if you run the SpringJCacheApp main class then you will see below output in the Eclipse console.

Explicit Disk Configuration:
Concurrency                : 16
Initial Segment Table Size : 64 slots
Segment Data Page Size     : 2KB
Fetching Person details
Cached Person Info: Person [id=1, name=Younker Patel]
Saved Person Info in cache: Person [id=1000, name=Soumitra Roy]
Has Person Info removed from cache: false
Is there still any Person Info in the cache: false

That’s All. Hope you got an idea on Spring JCache support example.

Source Code

download source code

Thanks for reading.

Leave a Reply

Your email address will not be published. Required fields are marked *