Caching Using Hazelcast In Spring Boot REST API

Table of Contents

Introduction

In this tutorial I am going to show you an example on caching using hazelcast in Spring Boot REST APIs. The cache or caching mechanism is used to store a piece of information frequently accessed in several places for request/response path. Therefore, it stores a copy of the given resource and serves it back when requested.

The performance of the web applications could be significantly improved by reusing the previously stored data. It also reduces latency and network traffic to fetch data and hence it reduces the time taken to represent the resource.

There are several kinds of caches but these can be grouped into two main categories – private or shared caches. A shared cache is a cache that stores responses for reuse by more than one user. A private cache is dedicated to a single user.

Hazelcast is a streaming and memory-first application platform for fast, stateful, data-intensive workloads on-premises, at the edge or as a fully managed cloud service.

Related Posts:

Prerequisites

Java 1.8+ (11 – 16), Spring Boot 2.6.4, Maven 3.8.2, Hazelcast 4.2.4 (used by Spring Boot 2.6.4), MySQL 8.0.26

Project Setup

You can create a maven based project in your favorite IDE or tool. The following pom.xml can be used in your project.

<?xml version="1.0" encoding="UTF-8"?>

<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>com.roytuts</groupId>
	<artifactId>spring-hazelcast-caching-example</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>11</maven.compiler.source>
		<maven.compiler.target>11</maven.compiler.target>
	</properties>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.4</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-devtools</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>

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

		<dependency>
			<groupId>com.hazelcast</groupId>
			<artifactId>hazelcast</artifactId>
		</dependency>

		<dependency>
			<groupId>com.hazelcast</groupId>
			<artifactId>hazelcast-spring</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

MySQL Table

I am using MySQL table to store data. Here is the teacher table which is created under roytuts database.

CREATE TABLE `teacher` (
  `id` int unsigned COLLATE utf8mb4_unicode_ci NOT NULL AUTO_INCREMENT,
  `name` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
  `expertise` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

The following data are inserted into the above table:

insert  into `teacher`(`id`,`name`,`expertise`)
values (1,'Bibhas Chandra Dhara','Statistics'),
(2,'UKR','System Programming'),(3,'New','Expert');

Application Properties

The following application.properties file is kept under class path folder src/main/resources with the following content.

#Spring Datasource
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/roytuts
spring.datasource.username=root
spring.datasource.password=root
 
#SQL related
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql=true
 
spring.jpa.hibernate.ddl-auto = none

I am not creating table from the entity class, so I have used the config key/value pair spring.jpa.hibernate.ddl-auto = none.

I am logging the SQL statement in the console with readable format using the following config:

spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql=true

The rest of the config defines the spring datasource.

Entity Class

The entity class that maps database table with the Java object.

package com.roytuts.spring.hazelcast.caching.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;
import javax.persistence.Table;

@Table
@Entity
public class Teacher implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@Column
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private int id;

	@Column
	private String name;

	@Column
	private String expertise;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getExpertise() {
		return expertise;
	}

	public void setExpertise(String expertise) {
		this.expertise = expertise;
	}

}

Repository Interface

I am using Spring Data JPA to get advantage of the built-in APIs. I am creating the following repository interface to query the database table.

package com.roytuts.spring.hazelcast.caching.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.roytuts.spring.hazelcast.caching.entity.Teacher;

public interface TeacherRepository extends JpaRepository<Teacher, Integer> {

}

Service Class

The service class is responsible for doing business logic for the application. Here is the simple methods that fetch data from teacher table.

package com.roytuts.spring.hazelcast.caching.service;

import java.util.List;

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

import com.roytuts.spring.hazelcast.caching.entity.Teacher;
import com.roytuts.spring.hazelcast.caching.repository.TeacherRepository;

@Service
public class TeacherService {

	@Autowired
	private TeacherRepository teacherRepository;

	@Cacheable("teachers")
	public List<Teacher> getTeachers() {
		return teacherRepository.findAll();
	}

	@Cacheable("teacher")
	public Teacher getTeacher(Integer id) {
		return teacherRepository.findById(id).orElse(new Teacher());
	}

}

The methods are annotated with hazelcast’s @Cacheable annotation that takes a key to distinguish a particular cache from other caches.

REST Controller

The REST controller class that exposes endpoints for fetching teacher details from the database via service class.

package com.roytuts.spring.hazelcast.caching.rest.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import com.roytuts.spring.hazelcast.caching.entity.Teacher;
import com.roytuts.spring.hazelcast.caching.service.TeacherService;

@RestController
public class TeacherRestController {

	@Autowired
	private TeacherService teacherService;

	@GetMapping("/teachers")
	public ResponseEntity<List<Teacher>> getTeachers() {
		return new ResponseEntity<List<Teacher>>(teacherService.getTeachers(), HttpStatus.OK);
	}

	@GetMapping("/teacher/{id}")
	public ResponseEntity<Teacher> getTeacher(@PathVariable Integer id) {
		return new ResponseEntity<Teacher>(teacherService.getTeacher(id), HttpStatus.OK);
	}

}

Spring Boot Main Class

A class with main method and @SpringBootApplication annotation will deploy the app into embedded Tomcat server.

package com.roytuts.spring.hazelcast.caching;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@EnableCaching
@SpringBootApplication
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}

}

Testing Hazelcast Caching

Once your application is up and running by executing the above main class, you can hit the following URL to check whether cache is working or not.

URL: http://localhost:8080/teachers

HTTP Request: GET

Response:

[{"id":1,"name":"Bibhas Chandra Dhara","expertise":"Statistics"},{"id":2,"name":"UKR","expertise":"System Programming"},{"id":20,"name":"Soumitra","expertise":"roytuts.com"}]

Now if you check the console, you will see that the database was queried for fetching data:

Hibernate: 
    select
        teacher0_.id as id1_0_,
        teacher0_.expertise as expertis2_0_,
        teacher0_.name as name3_0_ 
    from
        teacher teacher0_

Now hit the same URL again and this time you won’t see the above Hibernate query in the console log.

Similarly for the single teacher details, you can hit the URL as follows:

URL: http://localhost:8080/teacher/1

HTTP Request Method: GET

Response:

{"id":1,"name":"Bibhas Chandra Dhara","expertise":"Statistics"}

Now you will see the following log in the console for Hibernate:

Hibernate: 
    select
        teacher0_.id as id1_0_0_,
        teacher0_.expertise as expertis2_0_0_,
        teacher0_.name as name3_0_0_ 
    from
        teacher teacher0_ 
    where
        teacher0_.id=?

Now if you hit again and again for the same request, you will not see any Hibernate query in the console. It means only the first time datum is fetched from the database and next time onward datum is fetched from the cache for the same request.

In the service class if you notice, I have put two different keys for fetching all teachers and a particular teacher using @Cacheable annotation. You can also put a single cache key at the class level to cache data as shown below:

@Service
@Cacheable("teachers")
public class TeacherService {

	@Autowired
	private TeacherRepository teacherRepository;


	public List<Teacher> getTeachers() {
		return teacherRepository.findAll();
	}


	public Teacher getTeacher(Integer id) {
		return teacherRepository.findById(id).orElse(new Teacher());
	}

}

Java Config for Hazelcast

So far so good and I have used default configuration for the Hazelcast API. Now you can also config the hazelcast cache using Java config as shown below:

package com.roytuts.spring.hazelcast.caching.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.hazelcast.config.Config;
import com.hazelcast.config.EvictionConfig;
import com.hazelcast.config.EvictionPolicy;
import com.hazelcast.config.MapConfig;
import com.hazelcast.config.MaxSizePolicy;

@Configuration
public class HazelcastCacheConfig {

	@Bean
	public Config config() {
		Config config = new Config();
		config.setInstanceName("hazelcast");

		EvictionConfig evictionConfig = new EvictionConfig();
		evictionConfig.setEvictionPolicy(EvictionPolicy.LRU);
		evictionConfig.setMaxSizePolicy(MaxSizePolicy.FREE_HEAP_SIZE);

		MapConfig mapConfig = new MapConfig();
		mapConfig.setName("teacher-map").setBackupCount(2).setTimeToLiveSeconds(300).setEvictionConfig(evictionConfig);

		config.addMapConfig(mapConfig);

		return config;
	}

}

Now you can test your cache application in the same way I have tested with default configuration. The results will be same.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *