Caching Data

Accessing a database is an expensive operation and it takes time. When an application sends a request to the database, the request is first sent over the network. Then the request is processed by the database and finally the result is sent back over the network. This complete cycle takes time and if the database call is frequent, that might severely impact the application performance. So a better approach would be, cache the data at application side if possible to minimize the database call.

JDBC API doesn’t provide any such caching feature. But fortunately Hibernate provides that. In fact Hibernate supports two levels of cache –

  1. First-level cache (L1 cache)
  2. Second-level cache (L2 cache)
First-level cache

As we already know, each EntityManager instance is associated with a persistence context. This persistence context provides an environment where a set of entity instances are managed by an entity manager. The persistence context serves as a first level cache. 

If you try to retrieve an entity from the database, EntityManager first checks the persistence context. If the entity is present in the persistence context, no database call will occur and the existing entity instance will be returned. Only if the entity is not present in the persistence context, actual database call will happen. So, when you retrieve an entity for the very first time, it is retrieved from the database. Subsequent requests to retrieve the same entity instance in the same session will return the entity from persistence context and no SQL query will be executed.

To test this, let’s consider we have an User entity as shown below –

@Entity
@Table(name = "USER_DETAILS", schema = "demo")
public class User {
    @Id
    @Column(name = "USER_ID")
    private long userId;

    @Column(name = "USER_NAME")
    private String userName;

    @Column(name = "EMAIL")
    private String email;

    // getter and setter
}

Also assume, we have following data in the database –

Now try to retrieve the user with id 1 consecutively two times.

User user1 = entityManager.find(User.class, 1L);
User user2 = entityManager.find(User.class, 1L);

You will see, Hibernate will issue only one SELECT query to the database to retrieve the data for the first time.

select
    user0_.USER_ID as user_id1_2_0_,
    user0_.EMAIL as email2_2_0_,
    user0_.USER_NAME as user_nam3_2_0_ 
from
    DEMO.USER_DETAILS user0_ 
where
    user0_.USER_ID = 1;

For the next find() method, Hibernate will simply return the user instance from the first level cache (persistence context) and no database call will happen.

Points to remember
  1. The first level cache is always on, no need for any special configuration. Also you cannot turn it off. In fact there is no reason to do that.
  2. In the first level cache, one and only one reference can exist for a given entity.
  3. The first level cache is associated with a specific EntityManager object and other EntityManager objects can not access it.
  4. Once EntityManager is closed, associated cached objects are lost forever.
  5. If you call EntityManager.clear() remove, it will remove all entities from cache.
Second-level Cache

As we already discussed, the scope of the first level cache is EntityManager. Once an EntityManager is closed, corresponding first level cache is removed as well. Also, the first level cache is associated with a specific EntityManager object and it can not be shared with other EntityManager objects.

On the other hand, the scope of the second level cache is EntityManagerFactory. Second level cache is shared by all EntityManager instances created from the same EntityManagerFactory.

So, if you try to fetch an entity when second level caching is enabled –

  1. If the entity is already present in the first level cache, it is returned from there.
  2. If the entity is not present in the first level cache, Hibernate will check in the second level cache and if found, it is returned from there. But, before returning the entity, it will be added to the first level cache of the entity manager instance so that for the next invocation it can be returned from the first level cache itself.
  3. If the entity is not present in the first or second level cache, Hibernate will load it from the database and return it. But, before returning the entity, it will be added to both first level and second level cache.
Enable second level caching

Second level cache is not enabled by default and you have to enable it.

First of all, use <shared-cache-mode> element in the persistence.xml to specify shared cache mode value. Let’s check the available values in the SharedCacheMode enum.

public enum SharedCacheMode {
    ALL,
    NONE,
    ENABLE_SELECTIVE, 
    DISABLE_SELECTIVE,
    UNSPECIFIED
}

Let’s understand the meaning of individual values –

Value Description
ALL All entities are cached.
NONE None of the entities are cached.
ENABLE_SELECTIVE Caching is enabled only for those entities where Cacheable is true.
DISABLE_SELECTIVE Caching is enabled for all the entities except for those entities where Cacheable is false.
UNSPECIFIED Cache provider specific default will be applied.

For our example we’ll use ENABLE_SELECTIVE to have a full control.

<persistence>
    <persistence-unit name="demoPU1">
        <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
        <properties>
            .....
            .....
        </properties>
    </persistence-unit>
</persistence>

Then, you have to specify the cache provider in the persistence.xml file. Hibernate can work with various cache providers. In our example, we will use Ehcache.

<persistence>
    <persistence-unit name="demoPU1">
        <properties>
            .....
            .....
            <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
        </properties>
    </persistence-unit>
</persistence>

Also, add Ehcache dependency in the pom.xml file.

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-ehcache</artifactId>
    <version>5.6.9.Final</version>
</dependency>

Finally, mark the classes with @Cacheable annotation to make them eligible for second level caching. For example, let’s configure the User entity for second level caching.

@Cacheable // this is same as @Cacheable(true)
@Entity
public class User {
    …...
    …...
}

Now, let’s try following code to understand the behavior –

EntityManager entityManager1 = entityManagerFactory.createEntityManager();
User user1 = entityManager1.find(User.class, 1L);
User user2 = entityManager1.find(User.class, 1L);
entityManager1.close();

EntityManager entityManager2 = entityManagerFactory.createEntityManager();
User user3 = entityManager2.find(User.class, 1L);
entityManager2.close();

You will see, only single SELECT will be executed in database –

select
    user0_.USER_ID as user_id1_2_0_,
    user0_.EMAIL as email2_2_0_,
    user0_.USER_NAME as user_nam3_2_0_ 
from
    DEMO.USER_DETAILS user0_ 
where
    user0_.USER_ID = 1;

The reason is simple. When the find() method is invoked for the first time, as the user with id 1 is not present in both first and second level cache, it is selected from the database and then added to the first level cache of entityManager1. Also the user object is added to the second level cache.

Then when the user entity is queried the second time using entityManager1.find() method, the user object is returned from the first level cache of entityManager1.

Finally, when the user entity was queried the last time using entityManager2.find(), Hibernate couldn’t find it in the first level cache of entityManager2. So, Hibernate will inspect the second level cache. As the user object is present there, it will be returned from there without a database call.

If you remove the @Cacheable annotation from the User entity, you will see one more SELECT for the last find() method.

Remove object from second level cache

An object representation of the second level cache can be obtained using the getCache() method of EntityManagerFactory. It will return an instance of type javax.persistence.Cache.

Cache cache = entityManagerFactory.getCache();

This Cache interface provides few methods to remove entity from second level cache –

Method Description
evict(Class cls, Object primaryKey) Removes an entity based on primary key
evict(Class cls) Removes all entities of the specified type from the second level cache
evictAll() Removes all entities from the second level cache

So, the following code will remove the user entity having id 1 from the second level cache.

Cache cache = entityManagerFactory.getCache();
cache.evict(User.class, 1L);

That’s it for now. Hope you have enjoyed this tutorial. If you have any doubt, please ask in the comment section. I will try to answer that as soon as possible. Till then, bye bye.