Hazelcast Cache Spring Boot Example

Hazelcast is one of the popular second-level cache mechanism. In this post, we will see Hibernate/JPA Hazelcast Cache Spring Boot example using Oracle/MySQL/PostgreSQL/ from Scratch. We will also see how to use Hazecast IMap to store entity as a key-value pair.

Introduction.

We will have three parts in this tutorial. In the first part, we will perform a simple save and get operation.

We will create an entity an entity using some Rest API and will retrieve it from the database. While retrieving the entity, the first time it will hit the database. Second time onwards it will not hit the database, it will retrieve the data from HazelCast Cache(for a specific time). For example, If we configure TimeToLiveSeconds is 600 seconds, that means we are caching data for 5 minutes.

In the second part, we will see Hazelcast Cache Spring Boot CRUD Example using Spring’s @Cachable, @CacheConfig, @CacheEvict, and @CachePut annotation in the service implementation layer.

We will have the following rest endpoints to perform the CRUD operation.

Request method Rest APIs/Endpoints
POSThttp://localhost:9091/book/savebook
GEThttp://localhost:9091/book/{bookId}
PUThttp://localhost:9091/book/update
DELETEhttp://localhost:9091/book/{bookId}

In the third Part, we will store the id and entity as a key-value pair using com.hazelcast.core.IMap.

We will have a configuration class(HazelcastConfig.java) where we will create a bean of HazelcastInstance. The HazelcastInstance will contain configuration-related information(eg. EvictionPolicy and TimeToLive etc).

We are going to test our example using a postman.

Spring boot second level cache example using Redis.

Spring Boot Hazelcast Cache Configuration.

Maven Dependency.

<!– Hazelcast maven dependency –>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-spring</artifactId>
</dependency>

In this example, we are going to configure hazelcast programmatically. We will have a basic hazelcast configuration, will create a bean of HazelcastInstance. Hazecast provides Config and MapConfig class for configuration.

@Configuration
@EnableJpaRepositories(basePackages = "com.javatute.repository")
@EnableCaching
public class HazelcastConfig {

	@Bean
	public HazelcastInstance hazelcastInstance() {

		Config config = new Config();
		// MapConfig configuration
		MapConfig mapConfig = new MapConfig();
		mapConfig.setName("book");
		mapConfig.setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE));
		mapConfig.setEvictionPolicy(EvictionPolicy.LRU);
		mapConfig.setTimeToLiveSeconds(60);

		config.setInstanceName("javatute_hazelcast");
		config.addMapConfig(mapConfig);
		return Hazelcast.newHazelcastInstance(config);
	}
}

The Config class contains all configuration details to start HazelcastIstance. The Config instances can be shared between threads, but should not be modified once HazelcastInstance created using Config class.

The MapConfig class contains all configuration detail for Imap.

There are four possible values for EvictionPolicy(LRU – Least Recently Used, LFU – Least Frequently Used, NONE, and RANDOM- Randomly).

We can define how much time data should be there in the Hazelcast cache using mapConfig.setTimeToLiveSeconds(60).

Using this line Hazelcast.newHazelcastInstance(config) we are creating HazelcastInstance. The config object contains MapConfig.

Hazelcast Cache Spring Boot Save and Get Example.

We are going to use Hibernate, Spring Boot, Spring Data JPA, Hazelcast, and Oracle for this example. For testing purposes, we will use the postman.

Open eclipse and create maven project, Don’t forget to check ‘Create a simple project (skip)’ click on next.  Fill all details(GroupId – springboothazelcast, ArtifactId – springboothazelcast, and name – springboothazelcast) and click on finish. Keep packaging as the jar.

Modify pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>springboothazelcast</groupId>
	<artifactId>springboothazelcast</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springboothazelcast</name>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.2.RELEASE</version>
	</parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<!-- oracle maven dependency -->
		<dependency>
			<groupId>com.oracle</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.3</version>
		</dependency>
		<!-- Hazelcast maven dependency -->
		<dependency>
			<groupId>com.hazelcast</groupId>
			<artifactId>hazelcast-spring</artifactId>
		</dependency>
	</dependencies>
</project>
  

If you see any error for oracle dependency then follow these steps.

The directory structure for Spring Boot Hazelcast Example.

spring Boot hazelcast example

Boook.java

package com.javatute.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Book implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int bookId;

	@Column(name = "book_name")
	private String bookName;

	public int getBookId() {
		return bookId;
	}

	public void setBookId(int bookId) {
		this.bookId = bookId;
	}

	public String getBookName() {
		return bookName;
	}

	public void setBookName(String bookName) {
		this.bookName = bookName;
	}
}

Note – To use Hazelcast our entity must implement a Serializable interface otherwise we will get below exception.

com.hazelcast.nio.serialization.HazelcastSerializationException: There is no suitable serializer for class com.javatute.entity.Book

BookRepository.java –Define BookRepository extending the CrudRepositoty interface. See more about Crudrepository and its method here.

package com.javatute.repository;

import java.io.Serializable;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

import com.javatute.entity.Book;

@Repository
public interface BookRepository extends CrudRepository<Book, Serializable> {

}

BookService.java – interface

package com.javatute.service;

import org.springframework.stereotype.Component;

import com.javatute.entity.Book;

@Component
public interface BookService {
	public Book saveBook(Book book);
	public Book findById(int bookId);
}

Note – See here more about @Component, @Controller, @Service and @Repository annotations here.

BookServiceImpl.java

package com.javatute.serviceimpl;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.javatute.entity.Book;
import com.javatute.repository.BookRepository;
import com.javatute.service.BookService;

@Service("bookServiceImpl")
@CacheConfig(cacheNames = "book")
public class BookServiceImpl implements BookService {

	@Autowired
	private BookRepository bookRepository;

	@Transactional
	@Override
	public Book saveBook(Book book) {
		book = bookRepository.save(book);
		return book;
	}

	@Transactional
	@Cacheable
	public Book findById(int bookId) {
		Optional<Book> bookResponse = bookRepository.findById(bookId);
		return bookResponse.get();
	}
}

The @CacheConfig annotation used with the class and used to configure cache which applies to all methods defined inside the class.  For example cacheNames, we have not defined cacheNames for @Cachable annotation which is used with the findById() method. Since we have already defined cacheName using @Cachable at class level, the same cacheNames will happy for methods.

BookController.java

package com.javatute.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.javatute.entity.Book;
import com.javatute.service.BookService;

@RestController
@RequestMapping(value = "/book")
public class BookController {

	@Autowired
	private BookService bookService;

	@RequestMapping(value = "/savebook", method = RequestMethod.POST)
	@ResponseBody
	public Book saveBook(@RequestBody Book book) {
		Book bookResponse = bookService.saveBook(book);
		return bookResponse;
	}

	@RequestMapping(value = "/{bookId}", method = RequestMethod.GET)
	@ResponseBody
	public Book getBookDetails(@PathVariable int bookId) {
		Book bookResponse = bookService.findById(bookId);
		return bookResponse;
	}
}

See more details about @Controller and RestController here.

SpringMain.java

package com.javatute.main;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = "com.javatute.*")
@EntityScan("com.javatute.*")
public class SpringMain {
	public static void main(String[] args) {
		SpringApplication.run(SpringMain.class, args);
	}
}

Note – See more details about @ComponentScan here.

HazelcastConfig.java

package com.javatute.config;

import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import com.hazelcast.config.Config;
import com.hazelcast.config.EvictionPolicy;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MaxSizeConfig;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;

@Configuration
@EnableJpaRepositories(basePackages = "com.javatute.repository")
@EnableCaching
public class HazelcastConfig {

	@Bean
	public HazelcastInstance hazelcastInstance() {

		Config config = new Config();
		// MapConfig configuration
		MapConfig mapConfig = new MapConfig();
		mapConfig.setName("book");
		mapConfig.setMaxSizeConfig(new MaxSizeConfig(100, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE));
		mapConfig.setEvictionPolicy(EvictionPolicy.LRU);
		mapConfig.setTimeToLiveSeconds(60);

		config.setInstanceName("javatute_hazelcast");
		config.addMapConfig(mapConfig);
		return Hazelcast.newHazelcastInstance(config);
	}
}

Note – See more details about @Configuration annotations here.

application.properties

# Connection url for the database
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:XE
spring.datasource.username=SYSTEM
spring.datasource.password=oracle
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
# Show or not log for each sql query
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.hibernate.ddl-auto =create
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.Oracle10gDialect
server.port = 9091
#show sql values
#logging.level.org.hibernate.type.descriptor.sql=trace
hibernate.show_sql = true
#spring.jpa.hibernate.logging.level.sql =FINE
#show sql statement
#logging.level.org.hibernate.SQL=debug

Testing of Hazelcast Cache Spring Boot Save and Get Example using postman.

Let’s run the SpringMain class(run as java application).

Spring Boot Hazelcast cache Example

Perform save operation first using below REST API.

POSt – http://localhost:9091/book/savebook

Request data.

{
    "bookName": "Alchemist"
}
 
Response data.
 
{
    "bookId": 1,
    "bookName": "Alchemist"
}
 
spring boot hazelcast crud example
 
 

Perform retrieve operation first using below REST API.

GET – http://localhost:9091/book/{bookId}

 
 
Let’s verify the log select query should not execute if we retrieve data within 60 seconds.
 
While the first time performing retrieve/get operation, data will come from the database.
 
 
When we will perform the second time retrieve/get operation within 6o seconds, data will come from the Hazelcast Cache. Observe the console no select query should fire.
 

Spring Boot Hazelcast cache CRUD example.

We need to modify BookService.java, BookServiceImpl.java, and BookController.java only, the rest of code would be the same.

BookService.java – interface

package com.javatute.service;

import org.springframework.stereotype.Component;

import com.javatute.entity.Book;

@Component
public interface BookService {
	public Book saveBook(Book book);

	public Book findById(int bookId);
	
	public Book update(Book book);
	
	public String delete(int bookId);
}

BookServiceImpl.java

package com.javatute.serviceimpl;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.javatute.entity.Book;
import com.javatute.repository.BookRepository;
import com.javatute.service.BookService;

@Service("bookServiceImpl")
@CacheConfig(cacheNames = "book")
public class BookServiceImpl implements BookService {

	@Autowired
	private BookRepository bookRepository;

	@Transactional
	@Override
	@CachePut(value = "book", key = "#book.bookId")
	public Book saveBook(Book book) {
		book = bookRepository.save(book);
		return book;
	}

	@Transactional
	@Cacheable(value = "book", key = "#bookId")
	public Book findById(int bookId) {
		Optional<Book> bookResponse = bookRepository.findById(bookId);
		return bookResponse.get();
	}

	@CacheEvict(value = "book", key = "#bookId")
	public String delete(int bookId) {
		bookRepository.deleteById(bookId);
		return "Book deleted successfully";
	}

	@CachePut(key = "#book.bookId", value = "book")
	public Book update(Book book) {
		return bookRepository.save(book);
	}
}

BookController.java

package com.javatute.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.javatute.entity.Book;
import com.javatute.service.BookService;

@RestController
@RequestMapping(value = "/book")
public class BookController {

	@Autowired
	private BookService bookService;

	@RequestMapping(value = "/savebook", method = RequestMethod.POST)
	@ResponseBody
	public Book saveBook(@RequestBody Book book) {
		Book bookResponse = bookService.saveBook(book);
		return bookResponse;
	}

	@RequestMapping(value = "/{bookId}", method = RequestMethod.GET)
	@ResponseBody
	public Book getBookDetails(@PathVariable int bookId) {
		Book bookResponse = bookService.findById(bookId);
		return bookResponse;
	}

	@RequestMapping(value = "/update", method = RequestMethod.PUT)
	@ResponseBody
	public Book update(@RequestBody Book book) {
		Book bookResponse = bookService.update(book);
		return bookResponse;
	}

	@RequestMapping(value = "/{bookId}", method = RequestMethod.DELETE)
	@ResponseBody
	public String delete(@PathVariable int bookId) {
		String response = bookService.delete(bookId);
		return response;
	}
}

Testing of Spring Boot Hazelcast CRUD example using postman.

Let’s run the SpringMain class(run as java application).

Spring Boot Hazelcast cache Example

Perform save operation first using below REST API.

POSt – http://localhost:9091/book/savebook

Request data.

{
    "bookName": "Alchemist"
}
 
Response data.
 
{
    "bookId": 1,
    "bookName": "Alchemist"
}
 
spring boot hazelcast crud example
 
 

Perform retrieve operation first using below REST API.

GET – http://localhost:9091/book/{bookId}

 
 
Let’s verify the log select query should not execute if we retrieve data within 60 seconds.
 
While the first time performing retrieve/get operation, data will come from the database.
 
 
When we will perform the second time retrieve/get operation within 6o seconds, data will come from the Hazelcast Cache. Observe the console no select query should fire.
 

Perform update operation first using below REST API.

PUT – http://localhost:9091/book/update

This will update both hazelcast cache as well database.

spring Boot hazelcast cache example with Oracle and postgresql

Perform update operation first using below REST API.

DELETE- http://localhost:9091/book/{bookId}

This will delete records from hazelcast cache as well as the database.

Spring Boot Hazelcast example using IMap.

Hazelcast provides different data structures to store the elements. In this example, we are going to see how to implement Spring Boot Hazelcast second-level cache using com.hazelcast.core.Imap interface.

We need to do the following changes in BookServiceImpl.java. The rest of the code would be the same.

In this example, we are not going to use @Cacheable or @CacheConfig annotations provided by Spring. We will just define a POJO that contains two fields key and value.

CachedData.java

package com.javatute.entity;

import java.io.Serializable;

public class CachedData<E> implements Serializable {

	private static final long serialVersionUID = 1L;
	private int key;
	private E value;

	public int getKey() {
		return key;
	}

	public void setKey(int key) {
		this.key = key;
	}

	public E getValue() {
		return value;
	}

	public void setValue(E value) {
		this.value = value;
	}
}

BookServiceImpl.java

package com.javatute.serviceimpl;

import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IMap;
import com.javatute.entity.Book;
import com.javatute.entity.CachedData;
import com.javatute.repository.BookRepository;
import com.javatute.service.BookService;

@Service("bookServiceImpl")
public class BookServiceImpl implements BookService {

	@Autowired
	private BookRepository bookRepository;

	IMap<Integer, CachedData> hazelCastMap = null;

	@Autowired
	HazelcastInstance hazelcastInstance;

	@Transactional
	@Override
	public Book saveBook(Book book) {
		book = bookRepository.save(book);
		return book;
	}

	@Transactional
	public Book findById(int bookId) {
		CachedData dataPair = getCachedDataFromHazelcast(bookId);
		Book book = null;
		if (hazelCastMap != null && dataPair == null) {
			dataPair = new CachedData();
			Optional<Book> bookResponse = bookRepository.findById(bookId);
			book = bookResponse.get();
			dataPair.setKey(bookId);
			dataPair.setValue(book);
			putToCache(bookId, dataPair);
			return book;
		}
		if (dataPair.getValue() != null) {
			book = (Book) dataPair.getValue();
		}
		return book;
	}

	public void putToCache(int key, CachedData dataPair) {
		hazelCastMap.put(key, dataPair);
	}

	public CachedData getCachedDataFromHazelcast(int key) {
		hazelCastMap = hazelcastInstance.getMap("map_config");
		CachedData cachedDataPair = hazelCastMap.get(key);
		return cachedDataPair;
	}
}

In the above example(BookServiceImpl.java) we are using com.hazelcast.core.IMap interface to store data as key-value pair. The IMap(hazelCastMap) has been initialized using hazelcastInstance.getMap("map_config").

While performing the retrieve operation the first time findById() method will get invoked. Inside findById() we are calling getCachedDataFromHazelcast() method.

If the getCachedDataFromHazelcast() method doesn’t return cached data(for the given bookId), it will repository findById() method and stores the data into Hazelcast cache using the putToCache() method.

When we perform retrieve operation for the second time onwards it will retrieve data from Hazelcast cache using the getCachedDataFromHazelcast() method.

After these changes deploy and test the example using below rest APIs.

POST – http://localhost:9091/book/savebook
GET – http://localhost:9091/book/{bookId}

Spring Boot Hazelcast example with PostgreSQL Database.

The same example(first one or second one) we can run using the PostgreSQL database. We need to make the following changes.

We need to add PostgreSQL dependency instead of oracle dependency in pom.xml and also need to modify the application.properties file. The rest of the code would be the same.

Add below dependency in pom.xml.

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>

Modify application.properties file.

spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
server.port = 9091
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

Hazelcast Cache Spring Boot example with MySQL Database.

We need to make the following changes in pom.xml and application.properties file.

We need to add MySQL dependency in pom.xml and also need to modify the application.properties file. The rest of the code would be the same.

Add below dependency in pom.xml. instead of oracle or PostgreSQL database.

pom.xml

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/springbootcrudexample
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
server.port = 9091

Some Basic points about Hazelcast.

Let’s see some basic points related to Hazelcast which has been used in this tutorial.

  • The Hazelcast is an In-Memory Data Grid. In this tutorial, we are going to use com.hazelcast.core.IMap interface which used to stores the data as the form of a map(key-value pair).
  • Our entity classes must implement the Serializable interface in order to use Hazelcast(Did you noticed Book.java and CachedData.java both classes have been implemented Serializable interface).
  • We can create multiple HazelcastInstances and which we can see on the console.

Members {size:2, ver:2} [
Member [192.168.0.102]:5701 – bfc0b028-aee1-4461-a430-18d6c54cba09
Member [192.168.0.102]:5702 – 96267c82-75d1-4bb4-85e7-e527ce058f16 this
]

  • We can define Hazelcast configuration in separate hazelcast.xml file and we can read using ClasspathXmlConfig as below. We need to put the hazelcast.xml file in the resources folder.

public class HazelcastConfig {

@Bean
public HazelcastInstance hazelcastInstance() {
Config config = new ClasspathXmlConfig("hazelcast.xml");
config.setInstanceName("my_instance");
HazelcastInstance hz = Hazelcast.newHazelcastInstance(config);
return hz;
}
}

That’s all about Hazelcast Cache Spring Boot Example using MySQL/Oracle/PostgreSQL. Leave a comment in case of any doubt.

You may like.

Spring Data JPA example.

Hibernate association mapping and inheritance mapping example using Spring Boot.

Hibernate Hazelcast Cache Documentation.

Summary –  We have seen Hazelcast Cache Spring Boot Example using Oracle/MySQL/PostgreSQL.  We also covered the Hazelcast Cache Spring Boot CRUD example. Also, we covered how to store data as key-value pair in Hazelcast using IMap.