EhCache3 In Memory Caching for Performance Improvement

EhCache3 In Memory Caching

If you are reading this you already know that caching is mechanism to store static data In-Memory for faster access and avoid expensive database calls or any kind of service calls to get the data.

There are many ways to implement cache in java based applications, starting from very simple Map based caching to Apache Common JCS, EhCache, Apache Ignite, Hazelcast, Oracle Coherence and many more. Each of them have pros and cons.

I will show how to use EhCache for caching implementation

EhCache3 Implementation

For this example you need to have following jars in the class path -

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.4.0</version>
</dependency>
<dependency> <!-- We need this because ehcache uses slf4j for logging -->
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

I will showcase following 2 basic and very important aspects of InMemory caching - 
Item Expiry - If entry in the cache meets the expiry condition, it will be evicted (removed) from the cache. To standard type of expiry conditions are Time-To-Live and Time-To-Idle. I will use Time-To-Live

Cache Max Size - Maximum size up to which the cache can grow. The max size constraint can be set by byte size (KB, MB etc) or no on entries in the cache. I will use no of entries.

There are 2 ways to configure cache. XML and programmatic. I will use xml way because it's more readable and intuitive. 

Below is xml configuration for a cache of Person object by personId with max size of 5 entries and Time-To-Live 30 seconds.
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xmlns='http://www.ehcache.org/v3'
    xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd">

    <cache alias="PersonCache">
        <key-type>java.lang.Integer</key-type>
        <value-type>com.rakesh.caching.Person</value-type>
        <expiry>
            <ttl unit="seconds">30</ttl>
        </expiry>
        <resources>
            <heap unit="entries">5</heap>
        </resources>
    </cache>
</config>

Person.java
package com.rakesh.caching;

public class Person {
    private int personId;
    private String firstName;
    private String lastName;
    
    public Person(){}
    public Person(int personId, String firstName, String lastName) {
        super();
        this.personId = personId;
        this.firstName = firstName;
        this.lastName = lastName;
    }
    public int getPersonId() {
        return personId;
    }
    public void setPersonId(int personId) {
        this.personId = personId;
    }
    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 "Person [personId=" + personId + ", firstName=" + firstName + ", lastName=" + lastName + "]";
    }
}

AppCacheManager.java
package com.rakesh.caching;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;

import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.Configuration;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.xml.XmlConfiguration;

public class AppCacheManager {
    private static AppCacheManager instance = null;
    private CacheManager cacheManage = null;

    private AppCacheManager() {
        try {
            URL ehcacheXmlUrl = new File("./resources/ehcache.xml").toURI().toURL();
            Configuration xmlConfig = new XmlConfiguration(ehcacheXmlUrl);
            cacheManage = CacheManagerBuilder.newCacheManager(xmlConfig);
            cacheManage.init();
            System.out.println("Cache Initialized");
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    public static AppCacheManager getInstance() {
        if (instance == null) {
            instance = new AppCacheManager();
        }
        return instance;
    }
    
    public Cache getPersonCache(){
        return this.cacheManage.getCache("PersonCache", Integer.class, Person.class);
    }
}

MainApp.java
package com.rakesh.caching;

import java.util.Date;

import org.ehcache.Cache;

public class MyMain {

    public static void main(String[] args) throws Exception {
        new MyMain().doMain();
    }
    
    private void doMain() throws Exception{
        Cache personCache = AppCacheManager.getInstance().getPersonCache();
        Person p;
        for(int i=1; i<=7; i++){
            p = new Person(i, "firstName-"+1, "lastName-"+i);
            personCache.put(p.getPersonId(), p);
        }
        
        System.out.println("Entries in the Cache at: " + new Date());
        System.out.println("--------------------");
        for(int i=1; i<=7; i++){
            p = new Person(i, "firstName-"+1, "lastName-"+i);
            System.out.println("Key: " + i + ", Value: " +personCache.get(i));
        }
        
        System.out.println("\nWaiting for 40 seconds\n");
        //Wait for 40 seconds so that we reach cache expiry threshold which is 30 seconds
        Thread.sleep(1000*40);
        System.out.println("\nAfter 40 seconds wait\n");
        
        System.out.println("Entries in the Cache at: " + new Date());
        System.out.println("--------------------");
        for(int i=1; i<=7; i++){
            p = new Person(i, "firstName-"+1, "lastName-"+i);
            System.out.println("Key: " + i + ", Value: " +personCache.get(i));
        }
    }

}

Output
Cache Initialized
Entries in the Cache at: Sat Jan 06 14:31:04 EST 2018
--------------------
Key: 1, Value: null
Key: 2, Value: null
Key: 3, Value: Person [personId=3, firstName=firstName-1, lastName=lastName-3]
Key: 4, Value: Person [personId=4, firstName=firstName-1, lastName=lastName-4]
Key: 5, Value: Person [personId=5, firstName=firstName-1, lastName=lastName-5]
Key: 6, Value: Person [personId=6, firstName=firstName-1, lastName=lastName-6]
Key: 7, Value: Person [personId=7, firstName=firstName-1, lastName=lastName-7]

After 40 seconds wait

Entries in the Cache at: Sat Jan 06 14:31:44 EST 2018
--------------------
Key: 1, Value: null
Key: 2, Value: null
Key: 3, Value: null
Key: 4, Value: null
Key: 5, Value: null
Key: 6, Value: null
Key: 7, Value: null

Explanation

As you can see we added 7 items in the cache. Since cache max entry is set to 5, we found only 5 entries in the cache, 2 are null which means those are not present in the cache. After waiting for 40 seconds we checked cache again. Based on Time-To-Live of 30 seconds all entries are expired and nothing is found in the cache.

You can download all code from github https://github.com/rakeshprajapati1982/ehcache

As always comments and feedback are welcome.

Comments

Other Popular Posts

How to enable JPA eclipselink logging in WAS Liberty

Java Thread Local Storage explained in easiest practical way

EhCache3 as JCache (JSR-107) Implementation with Cache Statistics

Lambda Expression v/s Anonymous Inner Class - Java 8