Understanding Second Level Cache in Hibernate using Ehcache

Introduction :

Caching is all about application performance optimization and it sits between your application and the database to avoid the number of database hits as many as possible to give a better performance.Hibernate provide the lots of other features, one of the major benefit of using Hibernate in large application is it’s support for caching, hence reducing database queries and better performance.

Hibernate provide caching functionality in three layers,

1) First Level Caching: 

Fist level cache in hibernate is enabled by default and you do not need to do anything to get this functionality working. In fact, you can not disable it even forcefully.

Its easy to understand the first level cache if we understand the fact that it is associated with Session object. As we know session object is created on demand from session factory and it is lost, once the session is closed. Similarly, first level cache associated with session object is available only till session object is live. It is available to session object only and is not accessible to any other session object in any other part of application.

2) Second Level Caching:


The second level cache is responsible for caching objects across sessions. When this is turned on, objects will first be searched  in the session if it not found then delegates searching in the cache and if they are not found, a database query will be fired. Second level cache will be used when the objects are loaded using their primary key. This includes fetching of associations. Second level cache objects are constructed and reside in different memory locations.

I will explain more on second level cache with a example.

3) Query Caching:


Query Cache is used to cache the results of a query. When the query cache is turned on, the results of the query are stored against the combination query and parameters. Every time the query is fired the cache manager  checks for the combination of parameters and query. If the results are found in the cache, they are returned, otherwise a database transaction is initiated.  As you can see, it is not a good idea to cache a query if it has a number of parameters, because then a single parameter can take a number of values. For each of these combinations the results are stored in the memory. This  can lead to extensive memory usage.
We have to change the hibernate configuration to enable the query cache. This is done by adding the following line to the Hibernate configuration.

 <property name="hibernate.cache.use_query_cache">true</property>

Read this page to find pitfalls on query second level cache.

Understanding Second level caching:



When ever hibernate session try to load an entity, the very first place it look for cached copy of entity in first level cache (i,e hibernate session).If cached copy of entity is present in first level, it is returned as result of load method.If there is no cached entity in first level cache, then second level cache is looked up for cached entity.If second level cache has cached entity, it is returned as result of load method. But, before returning the entity, it is stored in first level cache also so that next invocation to load method for entity will return the entity from first level cache itself, and there will not be need to go to second level cache again.If entity is not found in first level cache and second level cache also, then database query is executed and entity is stored in both cache levels, before returning as response of load() method.Second level cache validate itself for modified entities, if modification has been done through hibernate session APIs.

If some user or process make changes directly in database, the there is no way that second level cache update itself until “timeToLiveSeconds” duration has passed for that cache region. In this case, it is good idea to invalidate whole cache and let hibernate build its cache once again.


Let me explain second level caching with a simple app.


Maven dependencies required:

    <properties>
        <hibernate.version>4.3.5.Final</hibernate.version>
        <ehcache-version>2.4.3</ehcache-version>
        <db.mysql.driver.version>5.1.30</db.mysql.driver.version>
        <slf4j-version>1.7.7</slf4j-version>
    </properties>


    <dependencies>
        <!-- hibernate dependencies-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
            <version>${hibernate.version}</version>
        </dependency>

        <!-- ehcache dependencies-->
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-core</artifactId>
            <version>${ehcache-version}</version>
        </dependency>

        <!-- database driver dependencies-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${db.mysql.driver.version}</version>
        </dependency>

        <!-- slf4j-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j-version}</version>
        </dependency>

    </dependencies>

    <build>
        <finalName>hibernate-second-level-cache-example</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.7</source>
                    <target>1.7</target>
                </configuration>
            </plugin>
        </plugins>
    </build>


For hibernate second level cache, they are many caching providers.Among Ehcache is popular caching framework.So we would need to add ehcache-core and hibernate-ehcache dependencies in our application. EHCache uses slf4j for logging, so I have also added slf4j for logging purposes.

Model Class: 

Lets take a simple User entity java class which helps in understanding second level cache using CRUD operations on User entity.

User.java

@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region = "user")
@Entity
@Table(name = "USER")
public class User implements java.io.Serializable {

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "USERID", nullable = false)
    private Integer userid;

    @Column(name = "USERNAME", length = 20)
    private String username;

    @Column(name = "PASSWORD", length = 20)
    private String password;

    @Column(name = "ROLE", length = 20)
    private String role;

    @Column(name = "ACTIVE")
    private boolean active;

//constructor,setters & getter,hashcode & equals,toString

}

If you observe we use concurrency strategy in the above model,so it is time we should discuss about concurrency strategy. A concurrency strategy is a mediator which responsible for storing items of data in the cache and retrieving them from the cache. If you are going to enable a second-level cache, you will have to decide, for each persistent class and collection, which cache concurrency strategy to use.

Four Types of Concurrency Strategies: 

Transactional: 

Use this strategy for read-mostly data where it is critical to prevent stale data in concurrent transactions,in the rare case of an update.

Read-write: 

Again use this strategy for read-mostly data where it is critical to prevent stale data in concurrent transactions,in the rare case of an update.

Nonstrict-read-write: 

This strategy makes no guarantee of consistency between the cache and the database. Use this strategy if data hardly ever changes and a small likelihood of stale data is not of critical concern.

Read-only:

 A concurrency strategy suitable for data which never changes. Use it for reference data only.

Hibernate Configuration:

<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <property name="hibernate.connection.driver_class">
            com.mysql.jdbc.Driver
        </property>
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost:3306/demoDB?createDatabaseIfNotExist=true
        </property>
        <property name="hibernate.connection.username">
            root
        </property>
        <property name="hibernate.connection.password">
            pramati
        </property>
        <property name="hibernate.show_sql">true</property>
        <property name="hbm2ddl.auto">create</property>

        <!-- you can make it false to disable second level cache -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

        <!-- to enable query cache uncomment below property
        <property name="hibernate.cache.use_query_cache">true</property> -->

        <!-- to provide ehcache configuration file with cache configuration
        <property name="net.sf.ehcache.configurationResourceName">/myehcache.xml</property> -->

        <mapping class="com.hibernate.cache.model.User" />

    </session-factory>
</hibernate-configuration>


In above xml, four properties are related to caching

hibernate.cache.use_second_level_cache is true then second level caching is enabled.
hibernate.cache.use_query_cache is true then query caching is enabled.
hibernate.cache.region.factory_class : 
Two values allowed to this property
The non-singleton EhCacheRegionFactory allows you to configure EHCache separately for each Hibernate instance using net.sf.ehcache.configurationResourceName property.
Where as SingletonEhCacheRegionFactory shares the same EHCache configuration among all Hibernate session factories.

net.sf.ehcache.configurationResourceName : is used to define the EHCache configuration file location, it’s an optional parameter and if it’s not present EHCache will try to locate ehcache.xml file in the application classpath.If ehcache.xml does not find in the class path it will load default ehcache-failsafe.xml which have default caching properties availble in ehcache-core.jar

           <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />

For more explination on each and every property on ehcache,please go with my previous article on Ehcache.

Second Level Cache Demo :

public class HibernateSecondLevelCacheDemo {

    public static void main(String[] args) {

        SessionFactory sessionFactory = buildSessionFactory();

        //enabling statistics
        final Statistics statistics = sessionFactory.getStatistics();
        statistics.setStatisticsEnabled(true);

        //Adding entries to user table
        Session session = sessionFactory.openSession();
        session.beginTransaction();
        User userOne = new User(1, "user1", "user1", "user", true);
        User userTwo = new User(2, "user2", "user2", "user", false);
        session.save(userOne);
        session.save(userTwo);
        session.getTransaction().commit();
        session.close();



        Session sessionOne = sessionFactory.openSession();
        Session sessionTwo = sessionFactory.openSession();

        Transaction transactionOne = sessionOne.beginTransaction();
        Transaction transactionTwo = sessionTwo.beginTransaction();

        //printing initial statistics
        printStatistics(statistics,null,0);

        User user1=(User)sessionOne.load(User.class, 1);
        printStatistics(statistics,user1,1);

        User user2=(User)sessionOne.load(User.class, 1);
        printStatistics(statistics,user2,2);

        User user3=(User)sessionOne.load(User.class, 2);
        printStatistics(statistics,user3,3);

        User user4=(User)sessionTwo.load(User.class, 1);
        printStatistics(statistics,user4,4);

        User user5=(User)sessionTwo.load(User.class, 2);
        printStatistics(statistics,user5,5);

        transactionOne.commit();
        transactionTwo.commit();
        sessionOne.close();
        sessionTwo.close();
        sessionFactory.close();

    }



    public static void printStatistics(Statistics statistics, User user , int count)
    {
        System.out.println("***************");
        System.out.println("Hit : "+ count);
        if(user != null)
            System.out.println("User Details :" + user.toString());
        System.out.println("Entity fetch count :" + statistics.getEntityFetchCount());
        System.out.println("Second level cache hit count : "+ statistics.getSecondLevelCacheHitCount());
        System.out.println("Second level cache put count : " + statistics.getSecondLevelCachePutCount());
        System.out.println("Second level cache miss count : " + statistics.getSecondLevelCacheMissCount());
        System.out.println("***************");
    }

    public static SessionFactory buildSessionFactory() {
        URL url = HibernateSecondLevelCacheDemo.class.getClassLoader().getResource("configuration/hibernate.cfg.xml");
        Configuration configuration = new Configuration();
        configuration.configure(url);
        StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
        return configuration.buildSessionFactory(ssrb.build());
    }

}

Output :


Hibernate: insert into USER (ACTIVE, PASSWORD, ROLE, USERNAME) values (?, ?, ?, ?)
Hibernate: insert into USER (ACTIVE, PASSWORD, ROLE, USERNAME) values (?, ?, ?, ?)
***************
Hit : 0
Entity fetch count :0
Second level cache hit count : 0
Second level cache put count : 0
Second level cache miss count : 0
***************
***************
Hit : 1
Hibernate: select user0_.USERID as USERID1_0_0_, user0_.ACTIVE as ACTIVE2_0_0_, user0_.PASSWORD as PASSWORD3_0_0_, user0_.ROLE as ROLE4_0_0_, user0_.USERNAME as USERNAME5_0_0_ from USER user0_ where user0_.USERID=?
User Details :User{userid=1, username='user1'}
Entity fetch count :1
Second level cache hit count : 0
Second level cache put count : 1
Second level cache miss count : 1
***************
***************
Hit : 2
User Details :User{userid=1, username='user1'}
Entity fetch count :1
Second level cache hit count : 0
Second level cache put count : 1
Second level cache miss count : 1
***************
***************
Hit : 3
Hibernate: select user0_.USERID as USERID1_0_0_, user0_.ACTIVE as ACTIVE2_0_0_, user0_.PASSWORD as PASSWORD3_0_0_, user0_.ROLE as ROLE4_0_0_, user0_.USERNAME as USERNAME5_0_0_ from USER user0_ where user0_.USERID=?
User Details :User{userid=2, username='user2'}
Entity fetch count :2
Second level cache hit count : 0
Second level cache put count : 2
Second level cache miss count : 2
***************
***************
Hit : 4
User Details :User{userid=1, username='user1'}
Entity fetch count :2
Second level cache hit count : 1
Second level cache put count : 2
Second level cache miss count : 2
***************
***************
Hit : 5
User Details :User{userid=2, username='user2'}
Entity fetch count :2
Second level cache hit count : 2
Second level cache put count : 2
Second level cache miss count : 2
***************

Where as

Fetch Count : if a entity is retervied from database using sql query.
Second level cache hit count : If a entity is retervied from Second level cache.
Second level cache put count : If a entity is added into second level cache.
Second level cache miss count : If a entity tried to retervie from Second level cache,but that entity is not exist in cache.

Hit0 : we can see all are 0,it means no entity is tried to retrive from cache.

Hit1 : user1 is loading from sessionOne.First it tries to load from First level cache(Session), as it is not present in session then tries to load from Second level cache, as it's also not exist in second level cache, so "second level cache miss count" will increase by 1.Then entity will load from Database and put in first level cache & second level cache,So "second level cache put count" is increased by 1.As entry is retervied from database so fetch count will increase by 1.

Hit2 : Again User1 is requested from SessionOne.As it is there in first level cache so retervied from Session.

Hit3 : user2 is loading from sessionOne,Same as Hit1.

Hit4 : user1 is requested from sessionTwo.Then first it tries in Frist level cache,So it does not exist then tries in second level cache.As User1 is already existing in second level cache so "Second level cache hit count" increase by 1.

Hit5 : user2 is requested from sessionTwo,It same as Hit4.

That’s all about Hibernate second level caching using EHCache example, I hope it will help you in configuring EHCache in your hibernate applications and gaining better performance.

you can download source from GitHub.

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.




Getting started with Ehcache 2.X with a simple demo.

Ehcache is an open-source, standards-based cache for boosting performance, offloading your database, and simplifying scalability. As a robust, proven, and full-featured solution, it is today’s most widely used Java-based cache. You can use Ehcache as a general-purpose cache or a second-level cache for Hibernate.

Here let me explaing how to use Ehcache with a simple mutithreading program.Lets start with dependencies required,Below are the depdendecies required to use Ehcache.


        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.0</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>


Configuration XML :
<?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="cache1"
           maxEntriesLocalHeap="1000"
           maxEntriesLocalDisk="10000"
           eternal="false"
           diskSpoolBufferSizeMB="20"
           timeToIdleSeconds="60"
           timeToLiveSeconds="300"
           memoryStoreEvictionPolicy="LFU"
           transactionalMode="off">
        <persistence strategy="localTempSwap"/>
    </cache>

    <cache name="cache2"
           maxEntriesLocalHeap="1000"
           maxEntriesLocalDisk="10000"
           eternal="false"
           diskSpoolBufferSizeMB="20"
           timeToIdleSeconds="120"
           timeToLiveSeconds="600"
           memoryStoreEvictionPolicy="LFU"
           transactionalMode="off">
        <persistence strategy="localTempSwap"/>
    </cache>

</ehcache>



Let me brieflly explain about attributes of Cache tag

  1. maxEntriesLocalHeap : Maximum no of elements allowed in memory.
  2. maxEntriesLocalDisk : Once Heap is full,then element overflow to disk memory.So this is maximum no of elements allowed in Disk.
  3. eternal : Sets whether elements are eternal. If eternal,  timeouts are ignored and the element is never expired.
  4. diskSpoolBufferSizeMB : This is the size to allocate the DiskStore for a spool buffer. Writes are made
        to this area and then asynchronously written to disk. The default size is 30MB.
  5. timeToIdleSeconds : Element will expire if idle time reachs to timeToIdleSeconds(in seconds).
  6. timeToLiveSeconds : Element will expire if life of the element reachs to timeToIdleSeconds(in seconds).
  7. memoryStoreEvictionPolicy : 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)
  8. transactionalMode :  To enable an ehcache as transactions, set the transactionalMode.
  9. diskStore : If heap memory is full,then elements overflow to disk.Diskstore is the path of disk directory.

There are many such attributes in xml configuration,please go through ehcache schema definition.


User Model Class :
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

}

CachedUserManager :

CachedUserManager will intract with cache and do CRUD operations on cache.

public class CachedUserManager<K extends Integer, V extends User> {

    private final Cache cache;

    public CachedUserManager(Cache cache) {
        this.cache = cache;
    }

    public void add(K k, V v) {
        cache.put(new Element(k, v));
    }

    public V getValue(K k) {
        return (V) cache.get(k).getObjectValue();
    }

    public boolean keyExist(K k) {
        return cache.get(k) == null ? false : true;
    }

    public void deleteElement(K k) {
        if (cache.get(k) != null) {
            cache.remove(k);
        }
    }

    public Cache getCache() {
        return cache;
    }
}

UserEhcacheDemo :

Here is the Demo code to add user objects into cache.This demo will explaing how to add User object to cache and making current thread to sleep, so that idle time of user objects reachs to timeToIdleSeconds(specified in xml) hence objects status will changed to expire and removed from cache.


public class UserEhcacheDemo {


    public static final String CACHE_1 = "cache1";
    public static final String CACHE_2 = "cache2";

    public static void main(String[] args) {

        CacheManager cacheManager = buildCacheManager();
        Cache cache1 = getCache1(cacheManager);
        Cache cache2 = getCache2(cacheManager);
        final CachedUserManager cachedUserManager1 = new CachedUserManager<Integer, User>(cache1);
        final CachedUserManager cachedUserManager2 = new CachedUserManager<Integer, User>(cache2);

        int noOfThreads = 2;
        ExecutorService execService = Executors.newFixedThreadPool(noOfThreads);
        execService.submit(new Runnable() {
            String threadName = "thread_1";
            @Override
            public void run() {
                //adding some users to cache1
                addUsersToCache(cachedUserManager1,threadName);

                //retrieve users from cache1
                Integer i = new Integer(1);
                while(i<5)
                {
                    //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 = cachedUserManager1.keyExist(i);
                    System.out.println("user"+i+ (exist?" exist":" not exist")+ " in cache1 with "+threadName);
                    i++;
                }


            }
        });
        execService.submit(new Runnable() {
            String threadName = "thread_2";
            @Override
            public void run() {
                //adding some users to cache2
                addUsersToCache(cachedUserManager2,threadName);

                //retrieve users from cache1
                Integer i = new Integer(1);
                while(i<5)
                {
                    //any random value between 30 to 60 sec
                    int sleepTime = getRandomSleepTime(30000, 60000);
                    System.out.println(threadName +" will sleep during "+sleepTime+" milliseconds");
                    try {
                        Thread.currentThread().sleep(sleepTime);
                    }catch (InterruptedException e)
                    {
                        //do nothing
                    }
                    boolean exist = cachedUserManager2.keyExist(i);
                    System.out.println("user"+i+ (exist?" exist ":" not exist ")+ "in cache2 with "+threadName);
                    i++;
                }
            }
        });

    }

    public static Cache getCache1(CacheManager cacheManager) {
        return cacheManager.getCache(CACHE_1);
    }

    public static Cache getCache2(CacheManager cacheManager) {
        return cacheManager.getCache(CACHE_2);
    }

    public static CacheManager buildCacheManager() {
        InputStream is = UserEhcacheDemo.class.getClassLoader().getResourceAsStream("com/ehcache/userCache.xml");
        return CacheManager.create(is);
    }

    private static void addUsersToCache(CachedUserManager cachedUserManager,String threadName) {
        Integer i = new Integer(1);
        while(i<5){
            cachedUserManager.add(i, new User(i, "user" + i, "password" + i, "role" + i, i));
            System.out.println("Added user"+ i + " to "+ cachedUserManager.getCache().getName() + " using " + threadName);
            i++;
        }
    }

    private static int getRandomSleepTime(int min, int max){
        return min + (int)(Math.random() * ((max - min) + 1));
    }

}


Above program will have mutiple outputs,Here is one among them

Added user1 to cache2 using thread_2
Added user2 to cache2 using thread_2
Added user3 to cache2 using thread_2
Added user4 to cache2 using thread_2
thread_2 will sleep during 37461 milliseconds
Added user1 to cache1 using thread_1
Added user2 to cache1 using thread_1
Added user3 to cache1 using thread_1
Added user4 to cache1 using thread_1
thread_1 will sleep during 11195 milliseconds
user1 exist in cache1 with thread_1
thread_1 will sleep during 12607 milliseconds
user2 exist in cache1 with thread_1
thread_1 will sleep during 16771 milliseconds
user1 exist in cache2 with thread_2
thread_2 will sleep during 44240 milliseconds
user3 exist in cache1 with thread_1
thread_1 will sleep during 10718 milliseconds
user4 exist in cache1 with thread_1
user2 exist in cache2 with thread_2
thread_2 will sleep during 56851 milliseconds
user3 not exist in cache2 with thread_2
thread_2 will sleep during 41211 milliseconds
user4 not exist in cache2 with thread_2



In the Demo program,we have two threads adding 4 users to respective cache.After adding users,trying to retrieving users from cache.While retrieving, threads to going to sleep.Sleep time is randomly calculated as mentioned in the program.If you remember we kept idle time to 1 and 2 mins in respective cache configuration.So if thread sleep for more then 1 min with out reading elements then such elements will expire and removed from cache.
In the above output, users like user4 not exist in cache1 and user3,user4 not exists in cache2.It mean idle time reached for these users,so expired.

You can download source from GitHub.