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 |
POST | http://localhost:9091/book/savebook |
GET | http://localhost:9091/book/{bookId} |
PUT | http://localhost:9091/book/update |
DELETE | http://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.
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).
Perform save operation first using below REST API.
POSt – http://localhost:9091/book/savebook
Request data.
{
"bookName": "Alchemist"
}
{
"bookId": 1,
"bookName": "Alchemist"
}
Perform retrieve operation first using below REST API.
GET – http://localhost:9091/book/{bookId}
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).
Perform save operation first using below REST API.
POSt – http://localhost:9091/book/savebook
Request data.
{
"bookName": "Alchemist"
}
{
"bookId": 1,
"bookName": "Alchemist"
}
Perform retrieve operation first using below REST API.
GET – http://localhost:9091/book/{bookId}
Perform update operation first using below REST API.
PUT – http://localhost:9091/book/update
This will update both hazelcast cache as well database.
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.
- Difference between Repository and CrudRepository
- Difference between CrudRepository and JpaRepository in Spring Data JPA.
- CrudRepository Methods 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.