SpringBoot Hazelcast cache with PostgreSQL backend

Performance is a big problem in software development. And Caching is one solution to speed up system. Hazelcast is an open source in-memory data grid. So in the tutorial, JavaSampleApproach will show you how to use SpringBoot Hazelcast cache with PostGreSQL backend.

Related posts:
How to work with Spring Cache | Spring Boot
SpringBoot Caffeine cache with PostgreSQL backend
Couchbase – How to create Spring Cache Couchbase application with SpringBoot

I. Technologies

– Java 1.8
– Maven 3.3.9
– Spring Tool Suite – Version 3.8.1.RELEASE
– Spring Boot: 1.5.7.RELEASE
– Hazelcast

II. SpringBoot Hazelcast

In the tutorial, we will create a SpringBoot project as below design:

Springboot Hazelcast Cache - Architecture

Spring Boot will auto-configure a HazelcastInstance when having Hazelcast on the classpath. And Spring Boot only configures HazelcastInstance if a Hazelcast configuration is found.

We use dependency hazelcast-spring:


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

<code>hazelcast-spring</code> had included below dependency:

<pre class="lang:xhtml">
<dependency>
	<groupId>com.hazelcast</groupId>
	<artifactId>hazelcast</artifactId>
</dependency>

We can configure Hazelcast via com.hazelcast.config.Config bean. Or use an hazelcast.xml configuration file.
In the application.properties, we specify hazelcast.xml configuration via:


spring.hazelcast.config=classpath:config/my-hazelcast.xml

An example of hazelcast.xml:

<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.9.xsd"
           xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <map name="customers">
        <max-size>300</max-size>
        <eviction-policy>LFU</eviction-policy>
        <time-to-live-seconds>60</time-to-live-seconds>
        <eviction-percentage>25</eviction-percentage>
    </map>

</hazelcast>

III. Practice

We create a SpringBoot project as below structure:

SpringBoot Hazelcast Cache - project structure

Step to do:
– Create a SpringBoot project
– Create data model
– Create JPA repository
– Implement cache service
– Initial data with CommandLineRunner
– Run and check results

1. Create a SpringBoot project

We use SpringToolSuite to create a SpringBoot project with below dependencies:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
      <dependency>
          <groupId>com.hazelcast</groupId>
          <artifactId>hazelcast-spring</artifactId>
      </dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

2. Create data model

Create a Customer data model:


package com.javasampleapproach.hazelcast.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
 
@Entity
@Table(name = "customer")
public class Customer implements Serializable{
	@Id
	private String id;
	
	@Column(name = "firstname")
	private String firstName;
 
	@Column(name = "lastname")
	private String lastName;
	
	public Customer(){
	}
	
	public Customer(String id, String firstName, String lastName){
		this.id = id;
		this.firstName = firstName;
		this.lastName = lastName;
	}
	
	public String getId() {
		return this.id;
	}
 
	public void setId(String id) {
		this.id = id;
	}
 
	public String getFirstName() {
		return this.firstName;
	}
 
	public void setFirstName(String firstName) {
		this.firstName = firstName;
	}
 
	public String getLastName() {
		return this.lastName;
	}
 
	public void setLastName(String lastName) {
		this.lastName = lastName;
	}
 
	@Override
	public String toString() {
		return String.format("Customer[ id=%s, firstName=%s, lastName=%s]", this.id, this.firstName, this.lastName);
	}
}

Note: need implements Serializable for data model to cache with Hazelcast.

3. Create JPA repository

– Create a CustomerRepository repository:


package com.javasampleapproach.hazelcast.repo;

import org.springframework.data.repository.CrudRepository;

import com.javasampleapproach.hazelcast.model.Customer;

public interface CustomerRepository extends CrudRepository{
}

– Open application.properties, add data source config:


spring.datasource.url=jdbc:postgresql://localhost/testdb
spring.datasource.username=postgres
spring.datasource.password=123
spring.jpa.generate-ddl=true

4. Implement cache service

4.1 Configure Hazelcast

Create a file /src/main/resources/config/hazelcast.xml:

<?xml version="1.0" encoding="UTF-8"?>
<hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.9.xsd"
           xmlns="http://www.hazelcast.com/schema/config"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <map name="customers">
        <max-size>300</max-size>
        <eviction-policy>LFU</eviction-policy>
        <time-to-live-seconds>60</time-to-live-seconds>
        <eviction-percentage>25</eviction-percentage>
    </map>

</hazelcast>

Open application.properties, specify hazelcast.xml location:


spring.hazelcast.config=classpath:config/hazelcast.xml

4.2 Implement Customer cache


package com.javasampleapproach.hazelcast.service.cache;

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.Component;

import com.javasampleapproach.hazelcast.model.Customer;
import com.javasampleapproach.hazelcast.repo.CustomerRepository;
 
@Component
@CacheConfig(cacheNames = {"customers"})
public class CustomerCache {
	
	@Autowired
	CustomerRepository customerRepository;
	
	@Cacheable(key="#id")
    public Customer getOnCache(String id){
    	System.out.println("############# Backend processing...");
    	
    	// simulation the time for processing
    	try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
		}
    	
    	return customerRepository.findOne(id);
    }
	
	@CachePut(key="#id")
    public Customer putOnCache(String firstName, String id){
		// find a customer in repository
		Customer cust = customerRepository.findOne(id);
		
		// modify above customer by first-name
		cust.setFirstName(firstName);
		
		// save to database
        return customerRepository.save(cust);
    }
     
    @CacheEvict(key = "#id")
    public void evict(String id){
    }
}

For details about {@Cacheable, @CachePut, @CacheEvict}, we can refer at How to work with Spring Cache | Spring Boot.

4.3 Implement Customer services


package com.javasampleapproach.hazelcast.service;

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

import com.javasampleapproach.hazelcast.model.Customer;
import com.javasampleapproach.hazelcast.service.cache.CustomerCache;
 
@Service
public class CustomerServices {
	
	@Autowired
	CustomerCache customerCache;
	
    public Customer putCustomer(String firstName, String id){
        return customerCache.putOnCache(firstName, id);
    }
     
    public Customer get(String id){
    	return customerCache.getOnCache(id);
    }
     
    public void evict(String id){
    	customerCache.evict(id);
    }
}

5. Initial data with CommandLineRunner


package com.javasampleapproach.hazelcast;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

import com.javasampleapproach.hazelcast.model.Customer;
import com.javasampleapproach.hazelcast.repo.CustomerRepository;

@EnableCaching
@SpringBootApplication
public class SpringBootHazelcastApplication implements CommandLineRunner{
	 
	@Autowired
	CustomerRepository customerRepo;
	
	public static void main(String[] args) {
		SpringApplication.run(SpringBootHazelcastApplication.class, args);
	}
	 
	@Override
	public void run(String... arg0) throws Exception {
		// initial data to PostGreSQL database 
		customerRepo.save(Arrays.asList(new Customer("1", "Jack", "Smith"), 
											new Customer("2", "Adam", "Johnson")));
	}
}

Note: @EnableCaching is used to enable Hazelcast.

6. Run and check results

– Run the SpringBoot project -> console’s Log:


...

c.h.instance.DefaultAddressPicker        : [LOCAL] [dev] [3.7.8] Prefer IPv4 stack is true.
c.h.instance.DefaultAddressPicker        : [LOCAL] [dev] [3.7.8] Picked [192.168.0.105]:5701, using socket ServerSocket[addr=/0:0:0:0:0:0:0:0,localport=5701], bind any local is true
com.hazelcast.system                     : [192.168.0.105]:5701 [dev] [3.7.8] Hazelcast 3.7.8 (20170525 - 4e820fa) starting at [192.168.0.105]:5701
com.hazelcast.system                     : [192.168.0.105]:5701 [dev] [3.7.8] Copyright (c) 2008-2016, Hazelcast, Inc. All Rights Reserved.
com.hazelcast.system                     : [192.168.0.105]:5701 [dev] [3.7.8] Configured Hazelcast Serialization version : 1
c.h.s.i.o.impl.BackpressureRegulator     : [192.168.0.105]:5701 [dev] [3.7.8] Backpressure is disabled
com.hazelcast.instance.Node              : [192.168.0.105]:5701 [dev] [3.7.8] Creating MulticastJoiner
c.h.s.i.o.impl.OperationExecutorImpl     : [192.168.0.105]:5701 [dev] [3.7.8] Starting 4 partition threads
c.h.s.i.o.impl.OperationExecutorImpl     : [192.168.0.105]:5701 [dev] [3.7.8] Starting 3 generic threads (1 dedicated for priority tasks)
com.hazelcast.core.LifecycleService      : [192.168.0.105]:5701 [dev] [3.7.8] [192.168.0.105]:5701 is STARTING
c.h.n.t.n.NonBlockingIOThreadingModel    : [192.168.0.105]:5701 [dev] [3.7.8] TcpIpConnectionManager configured with Non Blocking IO-threading model: 3 input threads and 3 output threads

Members [1] {
	Member [192.168.0.105]:5701 - fd1c39c4-9228-487d-baf2-f4065197fb8f this
}

...

– Check PostGreSQL customer table:

SpringBoot Hazelcast Cache - customer table

– Make request 1: http://localhost:8080/api/cachable?id=1

-> Results:
Service process slowly and,

Springboot Hazelcast Cache - request 1

Server has displayed a text on console:


############# Backend processing...

– Make request 2: http://localhost:8080/api/cachable?id=1
Now the response is faster because Customer with id = 1 has been cached before, the application just get data from cache storage.

Springboot Hazelcast Cache - request 2

– Make request 3: http://localhost:8080/api/cacheput?id=1&firstname=Peter
Message is returned on Browser:
Done
Now customer with id=1 is modified: firstname=’Peter’, NOT ‘Jack’.

– Make request 4: http://localhost:8080/api/cachable?id=1
Response is faster BUT the result is difference from first request:

Springboot Hazelcast Cache - request 4

– Make request 5 – Make a cache-evict request: http://localhost:8080/api/cacheevict?id=1
Browser displays:
Done
Now customer with id=1 was evicted from cache storage.

– Make request 6: http://localhost:8080/api/cachable?id=1
Now the behavior of the browser is the same as the first request because customer with id=1 was evicted to cache, and the method under @Cachable is executed.
Service process slowly and result:


############# Backend processing...

Springboot Hazelcast Cache - request 6

IV Sourcecode.

SpringBootHazelcast



By grokonez | October 5, 2017.

Last updated on April 25, 2021.



Related Posts


Got Something To Say:

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

*