Ehcache 3.0 new API fundamentals

It’s been 5 years since Ehcache released Ehcache 2.0.Now Ehcache 3.0 is one of the most feature rich caching APIs out there.In the meantime while some caching solutions took very different approaches on it all, the expert group on JSR-107, Terracotta included, put great efforts in trying to come up with a standard API in 3.0.Below are the features introduced in Ehcache 3.0

1) Ehcache 3.0 will likely “extend” the specification of JSR-107.
2) Ehcache 2.0 constrain caches on-heap or on-disk.3.0 introduced more tiers to cache data in (off-heap, Terracotta clustered).
3) 3.0 new API is ready for the immediate future that is Java 8.
4) Ehcache 3.0 API supporting Lamdas.
5) Introduced BootstrapCacheLoader (sync/async),ehanced Statistics,Search & Transaction.
6) Introduced Write Through,Write Behind,Read Through & Refresh Ahead Features.
7) Many more......

Today, let me explain fundamentals of ehcache 3.0 using a simple demo program.Before going to ehcache 3.0,I recommend to understand basics of ehcache 2.x api,please go with my previous post.

Ehcahce can build with two types of configurations.
      1) XMLConfiguration.
      2) Building configuration using API.

Building cache using XMLConfiguration :
Below are the dependencies required to use Ehcache 3.0.

<dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.0.0.m3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-jdk14</artifactId>
            <version>1.5.6</version>
        </dependency>

XML Configuration (userCache.xml):

<ehcache:config
        xmlns:ehcache="http://www.ehcache.org/v3"
        xmlns:jcache="http://www.ehcache.org/v3/jsr107">
    <!--<ehcache:service>
        <ehcache:default-serializers>
            <ehcache:serializer type="org.ehcache.spi.serialization.DefaultSerializationProvider">
            </ehcache:serializer>
        </ehcache:default-serializers>
    </ehcache:service>
    -->
    <ehcache:cache-template name="myTemplate">
        <ehcache:expiry>
            <ehcache:tti unit="minutes">5</ehcache:tti>
        </ehcache:expiry>
        <ehcache:eviction-prioritizer>LRU</ehcache:eviction-prioritizer>
        <ehcache:heap size="200" unit="entries"/>
    </ehcache:cache-template>
    <ehcache:cache alias="defaultCache" usesTemplate="myTemplate">
        <ehcache:key-type copier="org.ehcache.internal.copy.SerializingCopier">java.lang.Integer</ehcache:key-type>
        <ehcache:value-type copier="org.ehcache.internal.copy.SerializingCopier">com.newehcache.model.User
        </ehcache:value-type>
    </ehcache:cache>
    <ehcache:cache alias="userCache" usesTemplate="myTemplate">
        <ehcache:key-type copier="org.ehcache.internal.copy.SerializingCopier">java.lang.Integer</ehcache:key-type>
        <ehcache:value-type copier="org.ehcache.internal.copy.SerializingCopier">com.newehcache.model.User
        </ehcache:value-type>
        <ehcache:expiry>
            <ehcache:ttl unit="minutes">2</ehcache:ttl>
        </ehcache:expiry>
        <ehcache:eviction-veto>com.newehcache.evictionveto.UserEvictionVeto</ehcache:eviction-veto>
        <ehcache:eviction-prioritizer>LFU</ehcache:eviction-prioritizer>
        <ehcache:integration>
            <ehcache:listener>
                <ehcache:class>com.newehcache.listener.UserListener</ehcache:class>
                <ehcache:eventFiringMode>SYNCHRONOUS</ehcache:eventFiringMode>
                <ehcache:eventOrderingMode>ORDERED</ehcache:eventOrderingMode>
                <ehcache:eventsToFireOn>CREATED</ehcache:eventsToFireOn>
            </ehcache:listener>
        </ehcache:integration>
        <ehcache:heap size="500" unit="mb"></ehcache:heap>
    </ehcache:cache>

</ehcache:config>

With this XML file you can configure a CacheManager at creation time.Brief explanation of configuration xml.
The root element of our XML configuration is config.One <config> element is one Cachemanager.With Ehcache 3.0, however, you may create multiple
CacheManager instances using the same XML configuration file.There are three elements under config
1) Service : Service is extension point for specifying CacheManager managed services.Each Service defined in this way is managed with the
same lifecycle as the CacheManager.In above examples I am using default Services,that is DefaultSerializationProvider or org.ehcache.spi.copy.DefaultCopyProvider.
2) CacheTemplate : CacheTemplate is for <cache> elements to inherit from.A <cache> element that references a <cache-template> by its name. A <cache> can override these properties as it needs.
In the above xml,we declared a template called myTemplate with basic details like, cache expires if it idle for 5 mins and Heap can hold atmost 200 entires.
And in the userCache,we overrided template values by increasing HEAP entries and decreasing expire time.
3) Cache : A <cache> element represent a Cache instance that will be created and managed by the CacheManager.
Each <cache> requires the alias attribute which is uniqully identified by CacheManager.There are multiple elements inside cache
    1) key-type : Defines the type for the key in the cache.Takes a fully qualified class name.
    2) value-type: Defines the type for the Value in cache.Takes a fully qualified class name.
    3) expiry : Defines expiry for the Cache.You can add any of the expire as below.
        1) class : You can create user defined expire stategy by implementing org.ehcache.expiry.Expiry.
        2) tti : Cache should expire if not accessed for the defined time.Supports all types of time units.
        3) ttl : Cache should expire after the defined time.Supports all types of time units.
        4) none : Cache should never expire.Default is none.
    4) eviction-veto : UserDefined eviction-veto which implements org.ehcache.config.EvictionVeto.
    5) eviction-prioritizer :  Policy would be enforced upon reaching the maxEntriesLocalHeap limit. Default policy is Least Recently Used (specified as LRU). Other policies available - First In First Out (specified as FIFO) and Less Frequently Used (specified as LFU)
    6) integration : You can Integrate loaderwriters,writebehind or listener to the cache.In our xml we defined userListner which triggers event which adding entry into cache.
    7) heap : Heap only cache.No of entires in the HEAP allowed.
There are many such attributes in xml configuration,please go through schema definition.

User Model Class :

We use below simple model class for caching.
public class User implements Serializable{
    private Integer userid;
    private String username;
    private String password;
    private String role;
    private Integer tenantid;
    public User(Integer userid, String username, String password, String role, Integer tenantid) {
        this.userid = userid;
        this.username = username;
        this.password = password;
        this.role = role;
        this.tenantid = tenantid;
    }
//setters & getters & toString
}

UserListener :


public class UserListener implements CacheEventListener {
    @Override
    public void onEvent(CacheEvent event) {
        Integer id = (Integer)event.getKey();
        User user = (User)event.getNewValue();
        //do some work when event is triggered.
        if(user.getTenantid() > 50)
        {
            System.out.println("Listener invoked when adding user" + user.getUserid());
        }
    }
}

UserEvictionVeto :


public class UserEvictionVeto implements EvictionVeto {
    @Override
    public boolean test(Object value) {
        User user = (User) value;
        if(user.getTenantid() > 60)
        {
            return true;
        }
        return false;
    }
}

NewEhcacheXMLConfigurationDemo : 

This is the Demo class,which takes userCache.xml configuration and builds cacheManager that intern builds cache.

public class NewEhcacheXMLConfigurationDemo {

    public static int count = 0;
    public static void main(String[] args) {

        try {
            URL url = SimpleEhcacheDemo.class.getClassLoader().getResource("ehcache/userCache.xml");
            final CacheManager cacheManager = new EhcacheManager(new XmlConfiguration(url, SimpleEhcacheDemo.class.getClassLoader()));
            cacheManager.init();
            final Cache<Integer, User> userCache = cacheManager.getCache("userCache", Integer.class, User.class);
            int noOfThreads = 2;
            ExecutorService executorService = Executors.newFixedThreadPool(noOfThreads);
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    String threadName = "thread_1";
                    //adding users into cache
                    for (int i = 0; i < 3; i++) {
                        int userID = getCount();
                        addUser(userCache, userID);
                        System.out.println("Added user"+ userID + " to userCache using in " + threadName);
                    }
                    int i = 1;
                    while (i <= count) {
                        //any random value between 1 to 45 sec
                        int sleepTime = getRandomSleepTime(1000, 45000);
                        System.out.println(threadName + " will sleep during " + sleepTime + " milliseconds");
                        try {
                            Thread.currentThread().sleep(sleepTime);
                        } catch (InterruptedException e) {
                            //do nothing
                        }
                        boolean exist = userCache.containsKey(i);
                        System.out.println("user" + i + (exist ? " exist" : " not exist") + " using " + threadName);
                        i++;
                    }
                }
            });
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    String threadName = "thread_2";
                    //adding users into cache
                    for (int i = 0; i < 3; i++) {
                        int userID = getCount();
                        addUser(userCache, userID);
                        System.out.println("Added user"+ userID + " to userCache using in " + threadName);
                    }
                    int i = 1;
                    while (i <= count) {
                        //any random value between 1 to 60 sec
                        int sleepTime = getRandomSleepTime(1000, 60000);
                        System.out.println(threadName + " will sleep during " + sleepTime + " milliseconds");
                        try {
                            Thread.currentThread().sleep(sleepTime);
                        } catch (InterruptedException e) {
                            //do nothing
                        }
                        boolean exist = userCache.containsKey(i);
                        System.out.println("user" + i + (exist ? " exist" : " not exist") + " using " + threadName);
                        i++;
                    }
                }
            });
            try {
                //waiting until executor threads are done.
                executorService.shutdown();
                while (!executorService.awaitTermination(24L, TimeUnit.HOURS)) {
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            userCache.clear();
            cacheManager.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("Bye bye....");
    }
    private static void addUser(Cache<Integer, User> userCache, int id) {
        User user = new User(id, "user" + id, "user" + id, "user", new Random().nextInt(100) + 1);
        System.out.println(user);
        userCache.put(id, user);
    }
    public static int getCount() {
        return ++count;
    }
    private static int getRandomSleepTime(int min, int max) {
        return min + (int) (Math.random() * ((max - min) + 1));
    }
}

Brief explanation above code, two threads operating on a single cache(userCache) and each thread add 3 users into cache.First thread will add three users and then go to sleep for time 0-45sec while reading each user from cache.Mean while second thread will add three users and will go to sleep for (0 - 60 sec) while reading each user from cache. If elements in the cache are not operated for more then  expire time then they will evict from cache.In above example as threads are going to sleep,some users are not touched hence they may be evicted from cache.Also notice listener is invoking when user is added into cache.

You may see different outputs for each run of above code.Below is one

User{userid=1, username='user1', password='user1', role='user', tenantid=64}
User{userid=2, username='user2', password='user2', role='user', tenantid=41}
Listener invoked when adding user1
Added user1 to userCache using in thread_1
User{userid=3, username='user3', password='user3', role='user', tenantid=55}
Listener invoked when adding user3
Added user3 to userCache using in thread_1
User{userid=4, username='user4', password='user4', role='user', tenantid=9}
Added user4 to userCache using in thread_1
thread_1 will sleep during 12235 milliseconds
Added user2 to userCache using in thread_2
User{userid=5, username='user5', password='user5', role='user', tenantid=98}
Listener invoked when adding user5
Added user5 to userCache using in thread_2
User{userid=6, username='user6', password='user6', role='user', tenantid=85}
Listener invoked when adding user6
Added user6 to userCache using in thread_2
thread_2 will sleep during 10473 milliseconds
user1 exist using thread_2
thread_2 will sleep during 15808 milliseconds
user1 exist using thread_1
thread_1 will sleep during 25225 milliseconds
user2 exist using thread_2
thread_2 will sleep during 58012 milliseconds
user2 exist using thread_1
thread_1 will sleep during 13435 milliseconds
user3 exist using thread_1
thread_1 will sleep during 6692 milliseconds
user4 exist using thread_1
thread_1 will sleep during 25113 milliseconds
user5 exist using thread_1
thread_1 will sleep during 28972 milliseconds
user3 exist using thread_2
thread_2 will sleep during 58581 milliseconds
user6 exist using thread_1
user4 not exist using thread_2
thread_2 will sleep during 21540 milliseconds
user5 not exist using thread_2
thread_2 will sleep during 1090 milliseconds
user6 not exist using thread_2
Bye bye....

Building Cache Configuration using API:

Here is the way to manage cache using API.Below snippet will explain building cache configuration using api.

// building cache configuration
        CacheConfigurationBuilder<Integer,User> cacheConfigurationBuilder = CacheConfigurationBuilder.newCacheConfigurationBuilder();
        cacheConfigurationBuilder.withExpiry(new Expiry() {
            @Override
            public Duration getExpiryForCreation(Object key, Object value) {
                return new Duration(120, TimeUnit.SECONDS);
            }
            @Override
            public Duration getExpiryForAccess(Object key, Object value) {
                return new Duration(120, TimeUnit.SECONDS);
            }
            @Override
            public Duration getExpiryForUpdate(Object key, Object oldValue, Object newValue) {
                return null;
            }
        })
        .evictionVeto(new UserEvictionVeto())
        .usingEvictionPrioritizer(Eviction.Prioritizer.LFU)
        .withResourcePools(ResourcePoolsBuilder.newResourcePoolsBuilder().heap(200, EntryUnit.ENTRIES))
         // adding defaultSerializer config service to configuration
        .add(new DefaultSerializerConfiguration(CompactJavaSerializer.class, SerializerConfiguration.Type.KEY))
        .buildConfig(Integer.class, User.class);

        // building cache manager
        CacheManager cacheManager
                = CacheManagerBuilder.newCacheManagerBuilder()
                .withCache("userCache", cacheConfigurationBuilder.buildConfig(Integer.class, User.class))
                .build(false);
        cacheManager.init();
        Cache<Integer, User> preConfigured =
                cacheManager.getCache("userCache", Integer.class, User.class);
        User user1 = new User(1, "user1", "user1", "admin", 100);
        User user2 = new User(2, "user1", "user1", "student", 101);
        preConfigured.put(1, user1);
        preConfigured.put(2, user2);

        // asserting values from cache
        assertEquals(user1,preConfigured.get(1));
        assertEquals(user2,preConfigured.get(2));
        //removing cache from EhcacheManager
        cacheManager.removeCache("preConfigured");
        // Closing cache manager
        cacheManager.close();



You can download source from GitHub.




Show Comments: OR

0 comments: