DEV Community

priyankanandula
priyankanandula

Posted on

Database Caching with Spring Boot and Hazelcast

image

Signed in as @priyankanandula

After few days gap, I am back 😎

Good day πŸ˜ƒ, guys! I come back with an important concept during the execution of database operations using ORM tools in Spring Boot.

In this blog, we'll talk about caching and how to implement it using Hazel Cast, one of the cache providers.

Final Project Structure

image

Let's get started πŸ‘‡


Before we go into detail regarding caching, let's have a look at how ORM tools handle the given request.

image

  • when client access our application, our application, which uses ORM tools like hibernate to read the data from the database, will execute a select query internally on the database table to retrieve the data.

  • The data is then converted into an object, which is then passed to our application, which then sends it back to the client as required.

  • Every time a client requests data from our application or ORM tool, the select statement will be executed, and the process will repeat again.

  • Instead of repeatedly performing the same read operation, we employ caching or a cache.

Caching :

Caching stores data or objects in temporary locations. When a request comes in for the first time, this ORM tool or the caching framework will read the data, transform it to an object, and store it in a temporary location or on disc.

The next time a request comes in, these ORM frameworks will check to see if the data for that request is already in the cache. If it exists, no database select queries or database communication will be performed; simple take the object from cache, process it, and return it to the client.

Finally, we may increase our application's performance by using the Cache.

Let's get started with the implementation πŸ‘‡


Follow the steps below to utilise Hazel Cast or any other cache provider.
  1. Adding Dependencies

  2. Create Cache Configuration

  3. Enable and Use Caching

  4. Caching in Action

1. Adding Dependencies :

Add the following dependencies in POM.xml file.

  • spring-boot-starter-cache
  • hazelcast-spring
POM.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.priya.springweb</groupId>
    <artifactId>ProductRestAPI</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ProductRestAPI</name>
    <description>Demo project for Spring BootRestAPI</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
<!-- https://mvnrepository.com/artifact/com.hazelcast/hazelcast-spring -->
<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast-spring</artifactId>

</dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

</project>

Enter fullscreen mode Exit fullscreen mode

2. Create Cache Configuration :

Now we'll create the java configuration class that we'll need to create a Cache. This is where we'll give the Cache a name, size, and amount of objects that can be placed in it, among other things.

we can add Hazelcast configuration in one of the following manners:
  • Add hazelcast.yaml configuration OR
  • Add hazelcast.xml configuration OR
  • Define @bean with Hazelcast configuration in the source code

I am using the Java Based Configuration using @bean

package com.priya.springweb.cacheconfiguraions;



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


@Configuration
public class ProductCacheConfig {

    @Bean
    public Config cacheConfig() {
        return new Config()
                .setInstanceName("hazel-instance")
                .addMapConfig(new MapConfig()
                        .setName("product-cache")
                        .setTimeToLiveSeconds(5000)
                        .setMaxSizeConfig(new MaxSizeConfig(200,com.hazelcast.config.MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
                        .setEvictionPolicy(EvictionPolicy.LRU)
                        );
                }
}

Enter fullscreen mode Exit fullscreen mode
  • addMapConfig : We set all the values for the Cache in this MapConfig, such as name, size, time, and so on.It holds all the information for the Cache.

  • setName : The name of the cache should be specified. choose whatever name you want.

  • setTimeToLiveSeconds : This is the period of time the object will live in the Cache before being evicted.

  • setMaxSizeConfig : The Cache's MaxSizeConfig option has two values: one is the maximum number of objects the Cache can hold, and the other is the maximum amount of space the Cache can maintain.

  • setEvictionPolicy : We'll also need to give cache eviction information, such as when the cache should be cleaned, because the cache size will grow with time, and if we don't clear it, our application will crash.

We can employ a variety of cache policies, like as

1 Least Recently Used(LRU)

2 Least Frequently Used(LFU)

3 Random : Randomly pick the element from Cache and removed it.

4 NONE : It will do nothing.


3. Enable and Use Caching :

I'm using the Rest APIs that we created in my previous blog.

This should be referred to [https://dev.to/priyanka_nandula/create-rest-crud-api-with-springboot-springdata-jpa-3m6b]

package com.priya.springweb;

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


@SpringBootApplication
@EnableCaching
public class ProductRestApiApplication {

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

}

Enter fullscreen mode Exit fullscreen mode

@EnableCaching that will enable the caching for our application

package com.priya.springweb.entities;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Product implements Serializable{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    private String name;
    private String description;
    private int price;
    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 getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }

}

Enter fullscreen mode Exit fullscreen mode

Our Model class Should implements the Serializable interface because hazel cast or any other Cache providers like EH Cache will serialize our projects or Model Entities to a file system or in memory as required to a database.

package com.priya.springweb.controllers;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
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.RestController;

import com.priya.springweb.entities.Product;
import com.priya.springweb.repos.ProductRepository;

@RestController
public class ProductController {

    @Autowired
    private ProductRepository repository;

    @RequestMapping(value="/products",method=RequestMethod.GET)
    public List<Product> getProducts(){

        return repository.findAll();
    }

    @RequestMapping(value="/products/{id}",method=RequestMethod.GET)
    @Cacheable(value="product-cache",key="#id")
    public Product getProductById(@PathVariable("id") int id) {

        return repository.findById(id).get();

    }

    @RequestMapping(value="/products/",method=RequestMethod.POST)
    public Product createProduct(@RequestBody Product product) {

        return repository.save(product);
    }


    @RequestMapping(value="/products/",method=RequestMethod.PUT)
    public Product updateProduct(@RequestBody Product product) {

        return repository.save(product);
    }

    @RequestMapping(value="/products/{id}",method=RequestMethod.DELETE)
    @CacheEvict(value="product-cache",key="#id")
    public void  deleteProduct(@PathVariable("id") int id) {

    repository.deleteById(id);

    }
}

Enter fullscreen mode Exit fullscreen mode
  • We will be using Caching on the get a single product method.

  • Cacheable : we will also use @Cachable on the rest controllers or service classes. Here value is the Cache name we provided in the configuration class and Key is unique Key to store in Cache.

  • CacheEvict : cache evict information that is when should cache be cleaned.

After you made all these Changes Run the Application -->Run on Spring Boot App


4. Caching in Action :

To check the working of Caching we will execute the GET MEthod.

NOTE: Please remember the following few points.

  1. Make sure MySQL Server is running.
  2. Make sure you are running the SpringBoot project in embedded Tomcat Server

I'm using RestTemplate inside the JUnit Class to consume the Restful Services we created in the Controller Class.

  • Using the RestTemplate given by the spring web module, we can now consume restful web services or create a restful client.
  • Spring web enables us to both design and consume restful web services.
  • Rest Template is a class that performs various HTTP Methods(GET/PUT/POST/DELETE..).
  • The returned json is automatically deserialized into the Product class when using a spring or rest template.
Application.Properties File :
spring.datasource.url=jdbc:mysql://localhost:3306/myproducts
spring.datasource.username=root
spring.datasource.password=root

server.servlet.context-path=/productapi

productresapi.services.url=http://localhost:8080/productapi/products/

spring.cache.cache-names=product-cache
spring.cache.type=hazelcast
spring.jpa.show-sql=true
Enter fullscreen mode Exit fullscreen mode
package com.priya.springweb;

import static org.junit.Assert.assertEquals;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;

import com.priya.springweb.entities.Product;


@RunWith(SpringRunner.class)
@SpringBootTest()
public class ProductRestApiApplicationTests {

    @Value("${productresapi.services.url}")
    private String baseURL;

    @Test
    public void textGetProduct() {
        RestTemplate template=new RestTemplate();
        Product product=template.getForObject(baseURL+"2", Product.class);
        System.out.println("name :"+product.getName());
        assertEquals("Mac Book Pro", product.getName());



    }

}

Enter fullscreen mode Exit fullscreen mode
Available methods are:
  • getForObject(url, classType) – retrieve a representation by doing a GET on the URL. The response (if any) is unmarshalled to given class type and returned.
  • getForEntity(url, responseType) – retrieve a representation as ResponseEntity by doing a GET on the URL.
  • exchange(requestEntity, responseType) – execute the specified RequestEntity and return the response as ResponseEntity.
  • execute(url, httpMethod, requestCallback, responseExtractor) – execute the httpMethod to the given URI template, preparing the request with the RequestCallback, and reading the response with a ResponseExtractor.

"In my upcoming blogs, I'll go over RestTemplate in greater detail.."

Now Run this JUnit Test Case:

image

image

Test Runs Successfully. Now go through the logs Console.

image

As you can see, when we sent the Request for the first time, it executed the Select Query to get the data from the database.

Run the JUnit Test Case Again :

I've now cleared the console logs and rerunning the Test Case.

image

Now Hibernate did not execute the select query because the object was already cached.

image

NOTE : Instead of using RestTemplate within the Code, you can also use Postmansuiteand then check the logs Console.

RestTemplate can be used anywhere in the application, such as in the RestController or Service classes.

Here's a link to the code's GIT repository.[https://github.com/priyankanandula/SpringBoot/tree/feature/HazelCast_Cache/ProductRestAPI]

That's it, guys, that's how we use Hazel Cast to implement caching. I hope you found it interesting.

image

This is priyanka nandula signing off...

Top comments (2)

Collapse
 
devallacharan profile image
Sai Charan

Really awesome and detailed article. I have used different caching techniques in the client side applications like Angular but never thought something similar to that is possible even in the server side. Would definitely want to try out once!

Collapse
 
priyanka_nandula profile image
priyankanandula

Thank you 😊