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:
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
:
com.hazelcast hazelcast-spring
hazelcast-spring
had included below dependency:
com.hazelcast hazelcast
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:
III. Practice
We create a SpringBoot project as below 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:
org.springframework.boot spring-boot-starter-cache com.hazelcast hazelcast-spring org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-web org.postgresql postgresql runtime
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
:
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:
– Make request 1: http://localhost:8080/api/cachable?id=1
-> Results:
Service process slowly and,
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.
– 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:
– 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...