Spring Boot + Hazelcast Hello World Example | CodeUsingJava








Spring Boot + Hazelcast Hello World Example


In a previous tutorial we saw what is Hazelcast and it's architecture. In this tutorial we will be implementing a hello world example using Spring Boot and Hazelcast. We will be creating a Spring Boot application to save, retrieve and delete User Accounts for a Banking Application. We will be making use of mysql database and hazelcast.
To better understand the need for hazelcast we will be creating the application in following steps.
  • First we will create a monolithic Spring Boot Application
  • We will then be implementing local cache using HashMap
  • We will then deploy the Spring Boot Application as a distributed system
  • Finally we will be making use of Hazelcast for our Spring Boot Application

Video Tutorial

between
  • Monolithic Spring Boot Application -

    Monolithic Application
    This will be the standard directory layout for maven project structure-
    Spring Boot Hazelcast Maven Project
    We need to start by creating a Maven pom.xml(Project Object Model) file. The pom.xml file contains the project configuration details.
    <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.codeusingjava</groupId>
    	<artifactId>boot-hazel</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.2.5.RELEASE</version>
    		<relativePath /> <!-- lookup parent from repository -->
    	</parent>
    
    	<properties>
    		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    		<java.version>1.8</java.version>
    	</properties>
    
    	<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>
    
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<scope>runtime</scope>
    		</dependency>
    	</dependencies>
    
        <!-- This configuration is needed to run spring boot application as a jar -->
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    				<configuration>
    					<mainClass>com.codeusingjava.BankApplication</mainClass>
    				</configuration>
    			</plugin>
    		</plugins>
    	</build>
    
    </project>
    
    between
    Create the User model class with the jpa annotations for mapping java object to database table as follows-
    package com.codeusingjava.model;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Entity
    @Table(name = "user")
    public class UserAccount {
    
    	@Id
    	@Column(name = "id")
    	@GeneratedValue(strategy = GenerationType.IDENTITY)
    	private Long id;
    
    	@Column(name = "accountNumber")
    	private String accountNumber;
    
    	@Column(name = "name")
    	private String name;
    
    	@Column(name = "address")
    	private String address;
    
    	@Column(name = "balance")
    	private long balance;
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public String getAddress() {
    		return address;
    	}
    
    	public void setAddress(String address) {
    		this.address = address;
    	}
    
    	public long getBalance() {
    		return balance;
    	}
    
    	public void setBalance(long balance) {
    		this.balance = balance;
    	}
    
    	public String getAccountNumber() {
    		return accountNumber;
    	}
    
    	public void setAccountNumber(String accountNumber) {
    		this.accountNumber = accountNumber;
    	}
    
    }
    
    Create the repository by extending the JpaRepository. By extending JpaRepository we get a bunch of generic CRUD methods into our type that allows saving , deleting and retrieving Users.
    package com.codeusingjava.db;
    
    import org.springframework.data.jpa.repository.JpaRepository;
    
    import com.codeusingjava.model.UserAccount;
    
    public interface UserRepository extends JpaRepository<UserAccount, Long> {
    
    	UserAccount findByAccountNumber(String accountNumber);
    
    	UserAccount deleteByAccountNumber(String accountNumber);
    }
    
    
    Create the RestController for performing UserAccount operations. We will be autowiring the UserRepository and use it to perform database operations.
    package com.codeusingjava.controller;
    
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.codeusingjava.db.UserRepository;
    import com.codeusingjava.model.UserAccount;
    
    @RestController
    @RequestMapping(path = "users")
    public class UserController {
    
    	@Autowired
    	private UserRepository userRepository;
    
    	@GetMapping(path = { "/get/{accountNumber}" })
    	public UserAccount getUser(@PathVariable("accountNumber") String accountNumber) {
    		return userRepository.findByAccountNumber(accountNumber);
    	}
    
    	@PostMapping("/add")
    	public void createUser(@RequestBody UserAccount user) {
    		userRepository.save(user);
    	}
    
    	@DeleteMapping(path = { "/{accountNumber}" })
    	public UserAccount deleteUser(@PathVariable("accountNumber") String accountNumber) {
    		return userRepository.deleteByAccountNumber(accountNumber);
    	}
    
    }
    
    
    between
    Next we create the Spring Boot class with the SpringBootApplication annotation
    package com.codeusingjava;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class BankApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(BankApplication.class, args);
    	}
    }
    
    
    Finally create the application.properties file with database properties as follows-
    spring.datasource.url=jdbc:mysql://localhost/bank?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false
    spring.datasource.username=root
    spring.datasource.password=root
    spring.datasource.platform=mysql
    spring.datasource.initialization-mode=always
    spring.jpa.hibernate.ddl-auto=update
    spring.jackson.serialization.fail-on-empty-beans=false
    server.port=8081
    
    
    Start the Spring Boot Application and perform the insert/retrieve and delete operations using Advanced Rest Client.
    Spring Boot Hazelcast Rest Client Project

    Spring Boot Hazelcast Rest Client
  • We will be implementing a simple local cache for better performance

    Monolithic Cache Application
    In the SpringBoot Main class create a bean of type Map-
    package com.codeusingjava;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    
    import com.codeusingjava.model.UserAccount;
    
    @SpringBootApplication
    public class BankApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(BankApplication.class, args);
    	}
    
    	@Bean
    	public Map<String, UserAccount> accountMap() {
    		return new HashMap<>();
    	}
    }
    
    
    In the controller class autowire the accountMap and use it as a cache-
    package com.codeusingjava.controller;
    
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import com.codeusingjava.db.UserRepository;
    import com.codeusingjava.model.UserAccount;
    
    @RestController
    @RequestMapping(path = "users")
    public class UserController {
    
    	@Autowired
    	private UserRepository userRepository;
    
    	@Autowired
    	private Map<String, UserAccount> accountMap;
    
    	@GetMapping(path = { "/get/{accountNumber}" })
    	public UserAccount getUser(@PathVariable("accountNumber") String accountNumber) {
    		//first check if accountMap has the userAccount details, if yes then return it. Else fetch it from database.
    		UserAccount userAccount = (accountMap.get(accountNumber) != null) ? accountMap.get(accountNumber)
    				: userRepository.findByAccountNumber(accountNumber);
    		return userAccount;
    	}
    
    	@PostMapping("/add")
    	public void createUser(@RequestBody UserAccount user) {
    	    //save user account in cache
    		accountMap.put(user.getAccountNumber(), user);
    		userRepository.save(user);
    	}
    
    	@DeleteMapping(path = { "/delete/{accountNumber}" })
    	public UserAccount deleteUser(@PathVariable("accountNumber") String accountNumber) {
    		//remove from both cache and database
    		accountMap.remove(accountNumber);
    		return userRepository.deleteByAccountNumber(accountNumber);
    	}
    
    }
    
    
    between
    If we now start this application and test, it will work good and more efficiently.
  • We will now be deploying the application as a distributed microservice for better load balancing-
    Distributed Microservices Cache Application
    Open command prompt and start the application on different ports- Use the command -
    java -Dserver.port='port-number' -jar boot-hazel-0.0.1-SNAPSHOT.jar
    
    
    If we now suppose start the application on port 8081, 8082,8083
    Spring Boot Hazelcast Command Prompt
    We now insert multiple bank accounts using port 8081. If we now try to retrieve the bank account details using application running on port 8082 and 8083 then they will not be having the bank details in the accountMap Cache and will need to fetch it from database.
  • We will now be making use of Hazelcast cache

    Distributed Microservices Hazelcast Application
    In this tutorial we are making use of spring boot version 2.2.5.RELEASE. The managed hazelcast version provided by spring boot is 3.12.6. So we will be downloading the hazelcast-management-center-3.12.6 Go to Hazelcast downloads page and download hazelcast management center 3.12.6
    Hazelcast Management Control
    Unzip it and start the bat file-
    Hazelcast Management Control Start
    If we now go to localhost:8080 we see the management console. Currently there are no hazelcast instances.
    Spring Boot Hazelcast Hello World Example
    We will now modify the pom.xml to add the hazelcast dependency.
    <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.codeusingjava</groupId>
    	<artifactId>boot-hazel</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.2.5.RELEASE</version>
    		<relativePath /> <!-- lookup parent from repository -->
    	</parent>
    
    	<properties>
    		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    		<java.version>1.8</java.version>
    	</properties>
    
    	<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>
    
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    			<scope>runtime</scope>
    		</dependency>
    		<dependency>
    			<groupId>com.hazelcast</groupId>
    			<artifactId>hazelcast</artifactId>
    		</dependency>
    	</dependencies>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    				<configuration>
    					<mainClass>com.codeusingjava.BankApplication</mainClass>
    				</configuration>
    			</plugin>
    		</plugins>
    	</build>
    
    </project>
    
    
    between
    Configure Hazelcast instance. We will be making use of the Hazelcast provided IMap as the cache instead of the regular HashMap. Both IMap and the HashMap implement the java map interface.
    package com.codeusingjava;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import com.hazelcast.config.Config;
    import com.hazelcast.config.ManagementCenterConfig;
    import com.hazelcast.core.Hazelcast;
    import com.hazelcast.core.HazelcastInstance;
    import com.hazelcast.core.IMap;
    import com.codeusingjava.model.UserAccount;
    
    @SpringBootApplication
    @Configuration
    public class BankApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(BankApplication.class, args);
    	}
    
    	/*
    	 * @Bean public Map<String, UserAccount> accountMap() { return new
    	 * HashMap<>(); }
    	 */
    
    	@Bean
    	public Config hazelCastConfig() {
    		return new Config().setManagementCenterConfig(
    				new ManagementCenterConfig().setEnabled(true).setUrl("http://localhost:8080/hazelcast-mancenter"));
    
    	}
    
    	@Bean
    	public HazelcastInstance hazelcastInstance(Config hazelCastConfig) {
    		return Hazelcast.newHazelcastInstance(hazelCastConfig);
    	}
    
    	@Bean
    	public Map<String, UserAccount> accountMap(HazelcastInstance hazelcastInstance) {
    		return hazelcastInstance.getMap("accountMap");
    	}
    
    }
    
    
    Any object that we are making use for the Hazelcast IMap should be serializable. So we need to make changes to the UserAccount class to make it serializable.
    package com.codeusingjava.model;
    
    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;
    
    @Entity
    @Table(name = "user")
    public class UserAccount implements Serializable {
    
    	private static final long serialVersionUID = 1L;
    
    	@Id
    	@Column(name = "id")
    	@GeneratedValue(strategy = GenerationType.IDENTITY)
    	private Long id;
    
    	@Column(name = "accountNumber")
    	private String accountNumber;
    
    	@Column(name = "name")
    	private String name;
    
    	@Column(name = "address")
    	private String address;
    
    	@Column(name = "balance")
    	private long balance;
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public String getAddress() {
    		return address;
    	}
    
    	public void setAddress(String address) {
    		this.address = address;
    	}
    
    	public long getBalance() {
    		return balance;
    	}
    
    	public void setBalance(long balance) {
    		this.balance = balance;
    	}
    
    	public String getAccountNumber() {
    		return accountNumber;
    	}
    
    	public void setAccountNumber(String accountNumber) {
    		this.accountNumber = accountNumber;
    	}
    
    }
    
    between
    We are done with the changes. There will be no changes in the controller class as we are autowiring a map named accountMap in the contoller class and Hazelcast IMap also implements the map interface. If we now start multiple instances of our application we can see that the cache map is now updated in each of the instances. Also the hazelcast management console shows the memory utilization of the cluster.
    Spring Boot Hazelcast Example

Downloads-

Spring Boot + Hazelcast Hello World Example