Implement Hibernate Second-Level Cache With NCache

In this tutorial, we’ll explore implementing a second-level cache in Hibernate using NCache.

We’ll set up a Java application with Hibernate. Then we’ll configure NCache as the second-level cache. Finally, we’ll test the implementation to see how caching reduces the database load and improves performance.

Basics

Before we dive into the implementation, let’s understand the basics of Hibernate, NCache, and Hibernate second-level cache.

Hibernate

Hibernate is an open-source object-relational mapping (ORM) framework for Java applications. It simplifies the development of database interactions by mapping Java objects to database tables and vice versa.

To improve performance, Hibernate provides two levels of caching:

1. First-Level Cache

The first-level cache is associated with the Hibernate session and is enabled by default. It stores the objects retrieved during a session and eliminates the need to hit the database multiple times for the same object.

The first-level cache is limited to the scope of a session and is not shared across sessions.

It is also not persistent and is cleared when the session is closed or cleared explicitly.

2. Second-Level Cache

The second-level cache is shared across sessions and can be configured to cache data at the application level. It reduces the number of database hits by storing objects longer.

Second-level cache needs to be configured explicitly and can be implemented using various caching providers like NCache, Ehcache, etc.

NCache

NCache is a distributed caching solution for .NET and Java applications. It provides an in-memory data store that can be used to cache frequently accessed data and improve application performance.

NCache supports various caching topologies like replicated, partitioned, and client cache.

NCache can be used as a second-level cache in Hibernate to store and retrieve objects from the cache instead of hitting the database.

Code Setup

Let’s start by creating a Java application and setting up Hibernate to interact with the database.

We’ll use Maven to manage dependencies and build the project. The application will have an entity class to define the data and a client class to interact with the database.

First, we’ll test the application without caching to see the database interactions. Then, we’ll configure NCache as the second-level cache in Hibernate to cache the entity objects and reduce database hits.

Dependencies

We’ll start by adding the required dependencies for Hibernate and NCache in the pom.xml file:

<dependencies> <!-- Hibernate dependencies --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>6.6.1.Final</version> </dependency> <!-- NCache dependencies --> <dependency> <groupId>com.alachisoft.ncache</groupId> <artifactId>ncache-hibernate</artifactId> <version>5.3.3</version> </dependency> </dependencies>

Please note that the versions mentioned here may vary based on the latest releases. Make sure to use the appropriate versions for your project.

Entity Class

Next, let’s create an entity class to represent the data we want to cache. We’ll define a simple Customer class with id and name fields:

@Entity @Cacheable @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "CustomerRegion") public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // getters and setters }

The Customer class is annotated with @Entity and @Cacheable to define it as a cacheable entity. It has an id field annotated with @Id and @GeneratedValue to generate unique identifiers automatically.

We also use the @Cache annotation to specify the caching strategy and region for the entity.

NONSTRICT_READ_WRITE tells Hibernate to update the cache when data is read or written with eventual consistency.

The region attribute specifies the cache region where the data will be stored.

We’ll configure NCache to use this region to store the cached data.

Hibernate Configuration

We need to configure Hibernate to interact with the database and enable caching.

Let’s create a hibernate.cfg.xml file in the src/main/resources directory with the following configuration:

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property> <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">password</property> <property name="hibernate.dialect">org.hibernate.dialect.Oracle12cDialect</property> <property name="hibernate.hbm2ddl.auto">update</property> <property name="hibernate.show_sql">true</property> </session-factory> </hibernate-configuration>

In this configuration file, we specify the database connection details, dialect, and cache settings. We use Oracle as the database and configure Hibernate to update the schema automatically.

Client Class

Let’s create a client class to interact with the database and test the caching mechanism. We can write a main class to save and retrieve Customer objects using Hibernate:

public class HibernateClient { public static void main(String[] args) { SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory(); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); Customer customer = new Customer(); customer.setName("John Doe"); session.save(customer); transaction.commit(); session.close(); session = sessionFactory.openSession(); transaction = session.beginTransaction(); Customer retrievedCustomer = session.get(Customer.class, customer.getId()); System.out.println("Retrieved Customer: " + retrievedCustomer.getName()); transaction.commit(); session.close(); session = sessionFactory.openSession(); transaction = session.beginTransaction(); retrievedCustomer = session.get(Customer.class, customer.getId()); System.out.println("Retrieved Customer: " + retrievedCustomer.getName()); transaction.commit(); session.close(); } }

In this client class, we create a SessionFactory using the Hibernate configuration and open a session to interact with the database. We save a Customer object to the database and retrieve it using the id.

Then we call the retrieveCustomer method twice in two separate sessions to see the database interactions without caching.

We’ll observe that the database is hit twice to retrieve the same object:

Database is hit twice to retrieve the same object

As we can see, the Hibernate query is executed twice.

Setting up the NCache Server

To use NCache as the second-level cache in Hibernate, we need to set up an NCache server and configure it to store the cached data.

NCache provides a distributed caching solution that can be installed on Windows and Linux servers. We’ll create a cache cluster using NCache and configure it. Once the cache cluster is set up, we can connect to it from our Java application.

Enabling NCache as Second Level Cache in Hibernate

Once the NCache server is set up, we can configure Hibernate to use NCache as the second-level cache provider. We’ll update the Hibernate configuration file and specify the cache settings to enable caching.

Enabling Cache Regions

Let’s update the cache settings in the hibernate.cfg.xml file to enable NCache as the second-level cache provider:

<hibernate-configuration> <session-factory> <property name="hibernate.cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class">com.alachisoft.ncache.NCacheRegionFactory</property> ... </session-factory> </hibernate-configuration>

Here, we set the hibernate.cache.use_second_level_cache property to true to enable the second-level cache. We also specify the hibernate.cache.region.factory_class property to use the NCacheRegionFactory, an implementation of the JCacheRegionFactory, as the cache provider.

Configuring NCache Properties

NCache provides a set of properties to configure the cache settings in Hibernate. We can specify these in the ncache-hibernate.xml file:

<configuration> <application-config application-id="myapp" enable-cache-exception="true" default-region-name="DefaultRegion" key-case-sensitivity="false"> <cache-regions> <region name="CustomerRegion" cache-name="demoCache" priority="AboveNormal" expiration-type="Sliding" expiration-period="8"/> <region name="DefaultRegion" cache-name="demoCache" priority="default" expiration-type="None" expiration-period="0"/> </cache-regions> <database-dependencies> <dependency entity-name="hibernator.BLL.Customer" type="oledb" sql-statement="SELECT CustomerID FROM Customers WHERE CustomerID ='?';" cache-key-format="Customers#[pk]" connection-string="Provider=SQLOLEDB;Data Source=20.200.20.40,1433;Initial Catalog=Northwind;User ID=john;Password=1234;"/> </database-dependencies> </application-config> </configuration>

In this configuration file, we define the CustomerRegion cache region with the cache name, priority, expiration type, and expiration period. We set the expiration-type to Sliding with an expiration period of 8 seconds. This means that the cached data will expire after 8 seconds of inactivity and will be removed from the cache.

Additionally, we define a DefaultRegion with default settings for other entities that do not have a specific cache region. This is used as a fallback region for entities that are not explicitly configured.

Having multiple cache regions allows us to define different cache settings for different entities based on their requirements.

Next, we define a database dependency for the Customers entity. This is used to keep the cache in sync with the database and update/remove the cached data when changes are made in the database.

We specify the SQL statement to retrieve the CustomerID from the Customers table and the connection string to connect to the database.

The cache key format specifies how the cache key is generated based on the primary key of the entity.

Testing

Now that we have configured NCache as the second-level cache in Hibernate, let’s test the application to see how caching improves performance. We’ll run the client class again and observe the database interactions with caching enabled.

Run the client class again and observe the database interactions with caching enabled

When we run the client class, we’ll see that the first call to retrieve the Customer object hits the database to fetch the data. However, the second call to retrieve the same object will fetch it from the cache instead of hitting the database again. This demonstrates how caching reduces the database load and improves performance by serving data from the cache.

Benefits of Using NCache With Hibernate

Using NCache as the second-level cache in Hibernate provides several benefits:

  • Improved performance: NCache provides fast in-memory storage for cached data, reducing latency and improving throughput. It also provides async operations to update the cache in the background, reducing the impact on application performance.
  • Scalability: As the application scales, NCache can scale horizontally to handle large amounts of data and user requests. It can be deployed in a cluster and supports features like cache replication and partitioning to distribute the load.
  • Flexibility: NCache provides various caching topologies and configurations to meet different application requirements. Moreover, due to the use of cache regions, different entities can have different cache settings based on their needs. This allows fine-grained control over caching behavior.
  • Synchronization: NCache provides the option of database synchronization to keep the cache in sync with the database. This ensures that the cached data is up-to-date and reflects the latest changes made in the database.

Summary

In this tutorial, we explored implementing a second-level cache in Hibernate using NCache for Java applications.

We started by understanding the basics of Hibernate, NCache, and Hibernate second-level cache. Then, we set up a Java application with Hibernate and configured NCache as the second-level cache. Finally, we tested the implementation to see how caching improves performance by reducing database hits and serving data from the cache.

By using NCache with Hibernate, developers can enhance the performance, scalability, and reliability of their applications by leveraging the power of distributed caching. NCache provides a robust caching solution that integrates seamlessly with Hibernate and other Java frameworks, making it an ideal choice for caching data in Java applications.

Source:
https://dzone.com/articles/implement-hibernate-second-level-cache-with-ncache