NCache is an in-memory distributed caching solution with a robust Java client and extensive support for various Java integrations. This blog explores how the NCache Java client elevates your application caching capability, covering essential caching operations and how to configure advanced integrations like Hibernate and Spring with ease. Whether you’re looking to boost query performance or streamline data access across distributed environments, NCache offers a comprehensive solution for Java developers.
Basic Caching Operation with Java Client
The NCache Java client leverages an optimized network protocol, enabling applications to access high-performance caching capabilities seamlessly.
After installing NCache, adding Maven dependencies, importing NCache packages, and connecting to the cache, we can begin cache operations. For instance, we can add and retrieve items in our Java applications as demonstrated in the code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// Get customer from database string customerKey = $"Customer:ALFKI"; Customer customer = FetchCustomerFromDB(customerKey); // Get customer from database if not found in cache if (customer == null) { // Get customer from database customer = FetchCustomerFromDB("ALFKI"); cache.Add(customerKey, customer); System.out.println("Customer added to cache with key " + customerKey); // Item added in cache successfully } // Retrieve the item from the cache Customer customer = cache.get(customerKey, Customer.class); System.out.println("Customer with key " + customerKey + " has value " + customer); |
Further, NCache offers a wide range of options for data caching, including bulk operations for adding, retrieving, updating, and deleting items; along with asynchronous API for improved performance with these CRUD operations for its Java client. For more details on more advanced caching operations, refer to the NCache documentation CRUD Operations: An Overview with Java code examples.
To make cache data retrieval even smoother, NCache offers advanced caching features like SQL querying and indexing strategies. With NCache, you can even take advantage of existing third-party Java integrations. Let’s take a look at two of them.
NCache as Hibernate Second-Level Cache
Hibernate is a widely-used object-relational mapping (ORM) tool for Java that helps bridge the gap between its applications and databases. To minimize excessive database roundtrips, Hibernate employs caching mechanisms, specifically first-level and second-level caches. When retrieving objects from the database, it first checks the first-level cache, which is a per-session cache, unique to each session. Then, if necessary, the second level cache. This cache is optional and can be shared across multiple sessions. Hibernate does not provide any built-in implementations for the second-level cache; instead, it relies on third-party caching providers, such as NCache.
NCache can be used as a second-level cache provider for Hibernate without any code changes. By default, the second-level cache in Hibernate is disabled, but you can enable it and configure NCache as the provider through the Hibernate configuration file, hibernate.cfg.xml. Here is an example of how you can set it up in the configuration file:
1 2 3 4 5 6 7 8 9 |
<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> <property name="ncache.application_id">myapp</property> <!-- It configures NCache as a second-level cache provider, for Hibernate 3.6 and above ---> <property name= "hibernate.cache.use_query_cache">true<\property> JCacheRegionFactory </session-factory> </hibernate-configuration> |
NCache as a Second-level Provider
NCache can be used as a second-level cache provider to store entities, collections, and query results in a distributed manner, making them accessible across multiple nodes in a cluster. To configure this, an application identifier is needed to locate the appropriate settings for each application within the NCache configuration file for Hibernate, NCacheHibernate.xml
Hibernate uses regions to group objects under a name. With NCache, you can either set up separate cache instances for each region or use a single cache instance shared across multiple regions. We can even configure each region with different NCache caching features. We also specify all these configurations inside the NCacheHibernate.xml. For example:
1 2 3 4 5 6 7 8 9 |
<configuration> <application-config application-id="myapp" enable-cache-exception="true" default-region-name="DefaultRegion" key-case-sensitivity="false"> <cache-regions> <region name="ProductRegion" cache-name="demoCache" priority="BelowNormal" expiration-type="Absolute" expiration-period="8"/> <region name="OrderRegion" cache-name="demoCache" priority="Normal" expiration-type="Sliding" expiration-period="8" /> <region name="DefaultRegion" cache-name="demoCache" priority="default" expiration-type="None" expiration-period="0"/> </cache-regions> </application-config> </configuration> |
For more details about the NCacheHibernate.xml file, check Configure Cacheable Objects and Regions.
Even if we enable second-level caching, Hibernate doesn’t cache any objects by default. We have to mark our objects as cacheable either through annotations or mapping files. For example, let’s annotate a Product class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
@Entity @Table(name = "Products") @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region = "ProductRegion") public class Product implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ProductID") private int productId; @Column(name = "ProductName") private String productName; @Column(name = "SupplierID") private int supplierID; // Getters public int getProductId() { return productId; } public String getProductName() { return productName; } public int getSupplierID() { return supplierID; } // Setters public void setProductId(int productId) { this.productId = productId; } public void setProductName(String productName) { this.productName = productName; } public void setSupplierID(int supplierID) { this.supplierID = supplierID; } public Product() { // Default constructor required by Hibernate } } |
We can also use NCache to cache query results. We can improve overall application performance by caching the results of frequent complex queries. To learn how to use NCache for query caching with Hibernate, check Use Query Caching.
NCache Spring Integration
NCache also offers a Spring Framework integration. The Spring framework is an application framework and inversion of control container for Java platform. We can plug NCache into the Spring caching module, making it scalable and distributed.
When we use NCache with Spring, we offload our database by reducing the number of database trips to serve data, giving us better performance. To start using NCache with Spring, we need to annotate our configuration class. Like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Configuration class CachingConfiguration { @Bean public CacheManager cacheManager() { String resource = Path.of(System.getenv("NCHOME"), "config/ncache-spring.xml").toString(); SpringConfigurationManager springConfigurationManager = new SpringConfigurationManager(); springConfigurationManager.setConfigFile(resource); NCacheCacheManager cacheManager = new NCacheCacheManager(); cacheManager.setSpringConfigurationManager(springConfigurationManager); return cacheManager; } } |
Instead of using annotations, we can also use an XML file. Although, we’re better off using annotations since we can find type errors at compile time.
After changing the Spring configuration via either annotations or an XML file, we need to configure the actual caches. Inside the ncache-spring.xml
file, we define our cache instances with their properties like Expiration and Eviction policy. For example,
1 2 3 4 5 6 |
<application-config default-cache-name="demoCache"> <caches> <cache name="demoCache" ncacheid-instance="demoCache" priority="normal" expiration-type="absolute" expiration-period="300"/> </caches> </application-config> |
After configuring our Spring application and enabling caching, the next step is to annotate the methods we want to cache. For example, to cache a service we call from an API controller or other services in our application, we can write,
1 2 3 4 5 6 7 8 9 10 11 12 |
@Service public class ProductService { @Cacheable("ProductsCache") public String getProductNameByCode(String code) { return retrieveProductNameFromDatastore(code); } private String retrieveProductNameFromDatastore(String code) { // Image a query to retrieve a product name from a database return "Any name"; } } |
With all these steps, we’re ready to use NCache with Spring. For more details about configuring NCache with Spring, check Configuring Applications for using NCache as a Spring Caching Provider.
Conclusion
The NCache Java client supports caching features ranging from basic to advanced operations, including querying and indexing with SQL. It integrates seamlessly with Hibernate and Spring Framework through configuration and minimal code changes, and also supports the JCache API for distributed caching. For a distributed, scalable, and high-performance caching solution in Java, consider giving NCache a try.