Implement Hibernate Second-Level Cache With NCache
Set up a Java application with Hibernate, configure NCache as the second-level cache, and test the implementation to see how caching reduces the DB load.
Join the DZone community and get the full member experience.
Join For FreeIn 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:
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.
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.
Opinions expressed by DZone contributors are their own.
Comments