Thursday, December 31, 2020

Spring boot 2 and ehcache 3 example

 Learn to configure caching in spring boot application using ehcache 3.x. Learn to use annotation based cache config as well as manually updating cache with CacheManager.

1. Maven dependencies

In this example, we are using Spring boot version 2.1.6.RELEASE. Older spring boot versions support ehcache 2.x available under net.sf.ehcache package. New versions of Spring boot support ehcache 3.x available under org.ehcache package.

Ehcache version 3 is an implementation of a JSR-107 cache manager.

We need following dependencies to add caching capability.

  • spring-boot-starter-cache
  • ehcache (org.ehcache)
  • cache-api (javax.cache)
pom.xml
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>com.company</groupId>
    <artifactId>SpringBootEhcache</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
 
    <name>SpringBootEhcache</name>
    <url>http://maven.apache.org</url>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath />
    </parent>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <skipTests>true</skipTests>
    </properties>
 
    <dependencies>
        <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>
     
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2. application.properties

Spring’s auto-configuration finds Ehcache’s implementation of JSR-107. However, no caches are created by default because neither Spring nor Ehcache looks for a default ehcache.xml file. We must specifically provide the ehcache configuration file path which has to be used.

application.properties
spring.cache.jcache.config=classpath:ehcache.xml

3. ehcache.xml

In ehcache.xml file, configure the cache names and their attributes.

Find the complete list of attributes in the ehcache documentation.

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'>
 
    <service>
        <jsr107:defaults enable-statistics="true"/>
    </service>
 
    <cache alias="employeeCache">
        <key-type>java.lang.Long</key-type>
        <value-type>com.company.Employee</value-type>
        <expiry>
            <ttl unit="seconds">10000</ttl>
        </expiry>
        <listeners>
            <listener>
                <class>com.company.CustomCacheEventLogger</class>
                <event-firing-mode>ASYNCHRONOUS</event-firing-mode>
                <event-ordering-mode>UNORDERED</event-ordering-mode>
                <events-to-fire-on>CREATED</events-to-fire-on>
                <events-to-fire-on>UPDATED</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>EVICTED</events-to-fire-on>
            </listener>
        </listeners>
        <resources>
            <heap unit="entries">2000</heap>
            <offheap unit="MB">100</offheap>
        </resources>
    </cache>
</config>

4. CacheEventListener

We have also used the event listener which shall be logging the cache events when cache entries are created, removed or updated.

CustomCacheEventLogger.java
package com.company;
 
import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class CustomCacheEventLogger implements CacheEventListener<Object, Object> {
 
    private static final Logger LOG = LoggerFactory.getLogger(CustomCacheEventLogger.class);
 
    @Override
    public void onEvent(CacheEvent cacheEvent) {
        LOG.info("Cache event = {}, Key = {},  Old value = {}, New value = {}", cacheEvent.getType(),
                cacheEvent.getKey(), cacheEvent.getOldValue(), cacheEvent.getNewValue());
    }
}

5. @EnableCaching

It enables Spring’s annotation-driven cache management capability and enable support for proxy interceptors when @Cacheable annotated methods are invoked.

CacheConfig.java
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
 
@Configuration
@EnableCaching
public class CacheConfig {
 
}

6. @Cacheable Annotation

To cache data which is returned from a method call, we can use @Cacheable annotation on the method. Use it’s attributes cacheNames and key to refer to cache and key attribute of cache entry.

EmployeeManager.java
import java.util.HashMap;
 
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
 
@Service
public class EmployeeManager
{
    static HashMap<Long, Employee> db = new HashMap<>();
     
    static {
        db.put(1L, new Employee(1L, "Alex", "Gussin"));
        db.put(2L, new Employee(2L, "Brian", "Schultz"));
    }
     
    @Cacheable(cacheNames="employeeCache", key="#id")
    public Employee getEmployeeById(Long id) {
        System.out.println("Getting employee from DB");
        return db.get(id);
    }
}

7. Spring CacheManager API

Sometimes we may want to use the caching in cases where using annotations might not seems perfect solution. In those cases, we can use org.springframework.cache.CacheManager and org.springframework.cache.Cache abstraction to access and utilize ehcache for adding and accessing cache entries.

To use CacheManager, we must first autowire into a spring component and the use it’s getCache(name) method to get the cache instance by it’s name.

Once we have access to the cache, we can use it’s get() and put() methods to add and access cache entries.

//1
 
@Autowired
private CacheManager cacheManager;
 
//2
 
Cache cache = cacheManager.getCache("myCache");
cache.put(3L, "Hello");
String value = cache.get(3L).get();

8. Spring boot ehcache 3 example – demo

I am creating a model class Employee whose instances will be cached.

Employee.java
import java.io.Serializable;
 
public class Employee implements Serializable
{
    private static final long serialVersionUID = 5517244812959569947L;
     
    private Long id;
    private String firstName;
    private String lastName;
 
    public Employee() {
        super();
    }
 
    public Employee(Long id, String firstName, String lastName) {
        super();
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }
 
    //Getters and setters
 
    @Override
    public String toString() {
        return "Employee [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
    }
}

Now in demo application class, I am testing the annotation based cache access using @Cacheable annotation and manual cache access using CacheManager.

Application.java
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.ApplicationContext;
import org.springframework.context.annotation.Bean;
 
@SpringBootApplication
public class Application {
 
    @Autowired
    private CacheManager cacheManager;
     
    @Autowired
    private EmployeeManager employeeManager;
 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
 
    @Bean
    public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
        return args -> {
             
            //This will hit the database
            System.out.println(employeeManager.getEmployeeById(1L));
             
            //This will hit the cache - verify the message in console output
            System.out.println(employeeManager.getEmployeeById(1L));
             
            //Access cache instance by name
            Cache cache = cacheManager.getCache("employeeCache");
             
            //Add entry to cache
            cache.put(3L, new Employee(3L, "Charles", "Dave"));
             
            //Get entry from cache
            System.out.println(cache.get(3L).get());
        };
    }
}

Program output.

Console
//First hit to "employeeManager.getEmployeeById(1L)"
 
Getting employee from DB
Employee [id=1, firstName=Alex, lastName=Gussin]
2019-07-10 15:42:32.371  INFO 11956 --- [e [_default_]-0] com.company.CustomCacheEventLogger      
: Cache event = CREATED, Key = 1,  Old value = null, New value = Employee [id=1, firstName=Alex, lastName=Gussin]
 
//Second hit to "employeeManager.getEmployeeById(1L)"
Employee [id=1, firstName=Alex, lastName=Gussin]
 
//cache.put(3L, new Employee(3L, "Charles", "Dave"));
2019-07-10 15:42:32.399  INFO 11956 --- [e [_default_]-0] com.company.CustomCacheEventLogger       : Cache event = CREATED, Key = 3,  Old value = null, New value = Employee [id=3, firstName=Charles, lastName=Dave]
 
//System.out.println(cache.get(3L).get());
Employee [id=3, firstName=Charles, lastName=Dave]

Drop me your questions related to this spring boot 2 cache example using ehcache 3 in comments.

How to DROP SEQUENCE in Oracle?

  Oracle  DROP SEQUENCE   overview The  DROP SEQUENCE  the statement allows you to remove a sequence from the database. Here is the basic sy...