You are here
Home > Elasticsearch >

Spring Data Elasticsearch CRUD Examples Using Spring Boot

Spring Data Elasticsearch CRUD Examples Using Spring BootIn this article, we will learn how to develop ‘Spring Data Elasticsearch CRUD Examples Using Spring Boot’. We will start with the basics of Elasticseach as a theoretical part. In practical part we are going to discuss how to setup Elasticsearch in our system and make it ready to work. Then, we will move to the development of our example. Apart from CRUD operation with UI, we will also get into ‘How to create a REST API using Spring Boot and Elasticsearch.

What is Elasticsearch?

Elasticsearch offers you to store, search, and analyze large amounts of structured and unstructured data. It primarily works on the concept of indexing. Since it also capable of storing large amounts of structured and unstructured data, it is considered a NoSQL database. Hence, we cannot use SQL to query the data. However, it is the most popular for log analytics platform: the ELK Stack (Elasticsearch, Logstash and Kibana). Mainly, it became popular for search and log analysis, but now-a-days it is gaining popularity for one of the most popular database systems also.

Initially released in 2010, Elasticsearch (sometimes dubbed ES) is a modern search and analytics engine which is based on Apache Lucene. Completely open source and built on Java programming language. However, unlike most NoSQL databases, Elasticsearch has a strong focus on search capabilities.

Indexing is the process of adding data to Elasticsearch. This is because when we feed data into Elasticsearch, the data is placed into Apache Lucene indexes. This makes sense because Elasticsearch uses the Lucene indexes to store and retrieve its data. Elasticsearch provides more efficient and flexible indexing compared with relational databases. It acts as a near real-time search platform, meaning once records are indexed, they can be searched in as short as one second.

Let’s talk about some pros and cons of using Elasticsearch before the development of Spring Data Elasticsearch CRUD Examples Using Spring Boot.

What are the advantages of Using Elasticsearch?

Below are the list of major advantages that we can have in Elasticsearch.

1) Elasticsearch is a highly scalable, enterprise-level and open source platform to store, search, and analyze large amounts of structured and unstructured data.

2) Unlike typical SQL databases that retrieves the searched data in more than 10 seconds, ElasticSearch can do it in less than 10 ms.

3) ELasticsearch offers high scalability. It can be scaled up to a large amount of servers and store large amount of gigabytes of data. It is built to run smoothly on any system or in any cluster containing multiple numbers of nodes.

4) Apart from perfect handling of search queries, Elasticsearch is strong enough to manage large volumes of data with its distributed architecture.

5) Elasticsearch uses JSON (JavaScript Object Notation) as the serialization format for documents to store complex entities, and indexes all the records by itself. JSON serialization has become a standard format for NoSQL database and it is compatible with various programming languages.

6) Elasticsearch doesn’t require any data definition. It accepts JSON documents and detects the data type, index the records and make it searchable.

What are the disadvantages of Using Elasticsearch?

1) Elasticsearch supports handling of requests in the form of JSON only. Unlike Apache Solr, Elasticsearch does not have multi-language support for handling request and response data.

2) Elasticsearch is good for small use cases only in terms of data storage.

3) Sometimes, the problem of split-brain situations occurs in Elasticsearch.

How To Setup Elasticsearch?

Before starting the development of Spring Data Elasticsearch CRUD Examples Using Spring Boot, let’s create a setup of Elasticsearch if not done already.

1) Download Elasticsearch from the Elasticsearch download link.

2) Once you finished downloading the zip file, then extract it. Right Click on Elasticsearch zip file –> Extract Here. Once you extract, you will get a folder named as Elasticsearch with a version number as a suffix.

3) Open the command prompt under the Elasticsearch folder.

4) Enter the following command to run the Elasticsearch.

.\bin\elasticsearch.bat

Press Enter. If everything is fine then Elasticsearch will start running on.

5) In order to test the running Elasticsearch, open your browser and hit the URL http://localhost:9200. You will see some details of the running Elasticsearch instance.

Software/Technologies Used in the Examples

Sometimes some version conflicts with other version. Hence, listing down the combinations that are tested to be working with each other. Below is the tested combination of software that are used to develop Spring Data Elasticsearch CRUD Examples Using Spring Boot. It also makes the implementation flawless.

1) Elasticsearch 8.5.1 (download Elasticsearch)
2) Maven 3.8.1
3) JDK 17 or later
4) Spring Boot 2.7.5
5) IDE – STS 4.7.1. RELEASE

Jars Used

Below are the list of major jars used in these examples. They might be useful to cross verify in case if you face any issue in execution.

1) Spring Boot Starter Data Elasticsearch 2.7.5
2) Spring Data Elasticsearch 4.4.5
3) Elasticsearch 7.17.6
4) Spring Framework 5.3.23
5) Lombok 1.18.24

How to develop Spring Data Elasticsearch CRUD Examples Using Spring Boot?

Use-case Details

Let’s assume that we have to develop an Invoice Processing Application. As the application name suggests, we must have an Invoice entity in this application. In this article, we will develop Spring Data Elasticsearch CRUD Examples Using Spring Boot for Invoice as a model/entity.

Let’s develop Spring Data Elasticsearch CRUD Examples Using Spring Boot step by step as below:

Step#1: Create a Spring Boot Starter Project using STS

Here, we are using STS (Spring tool Suite) as an IDE to develop the example. While creating Starter Project select ‘Spring Web’, ‘Spring Data Elasticsearch‘, ‘Thymeleaf’, ‘Lombok’ and ‘Spring Boot DevTools’ as starter project dependencies. Here ‘Lombok’ and ‘Spring Boot Dev Tools’ are optional to add. We are using Lombok to avoid boilerplate codes and Spring Boot Dev Tools to avoid restarting tomcat server multiple times while testing the application. Apart from that, we need to add one additional dependency of ‘jakarta.json-api’

Even If you don’t know how to create Spring Boot Starter Project, Kindly visit a separate article on How to create a starter project Using Spring Boot?. Additionally, if you are eager to know more about Lombok, then you may visit a separate article on Lombok.

Below are the dependencies that we have used to develop this Spring Data Elasticsearch CRUD Examples Using Spring Boot.

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

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

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

<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
</dependency>

Step#2: Create a Configuration class InvoiceConfig.java

This configuration class is to connect with Elasticsearch DB which is running in our local system. Please pay extra attention on the import statements.

InvoiceConfig.java

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class InvoiceConfig {

   @Bean
   public RestClient getRestClient() {
      RestClient restClient = RestClient.builder(new HttpHost("localhost", 9200)).build();
      return restClient;
   }

   @Bean
   public ElasticsearchTransport getElasticsearchTransport() {
      return new RestClientTransport(getRestClient(), new JacksonJsonpMapper());
   }

   @Bean
   public ElasticsearchClient getElasticsearchClient(){
      ElasticsearchClient client = new ElasticsearchClient(getElasticsearchTransport());
      return client;
   }
}

Here, getRestClient( ) method is used to configure the URL and port number of the currently running Elasticsearch.

getElasticsearchTransport( ) method returns the Transport Object in order to automatically map our Model Class to JSON and integrates them with API Client.

getElasticsearchClient( ) It returns an object of ElasticsearchClient, which is further used to perform all query operations with Elasticsearch.

Step#3: Create Entity (model) class as Invoice.java

Since our use-case for this example is Invoice Processing, we will create an entity class as Invoice.java as shown below.

Invoice.java

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import lombok.Data;

@Document(indexName = "invoices")
@Data
public class Invoice {

   @Id
   private String id;

   @Field(type = FieldType.Text, name = "name")
   private String name;

   @Field(type = FieldType.Text, name = "number")
   private String number;

   @Field(type = FieldType.Double, name = "amount")
   private Double amount;
}

Step#4: Create Repository class for DB access

Next step is to create one Repository class. Please note that we should create a separate repository interface for each Entity in the application. This is also applicable for classes taking part in other layers accordingly. Since we have only one Entity, we will create one Repository Interface for now. As a naming convention, we will create a repository interface as InviceRepository.java as below.

InvoiceRepository.java

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

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

import com.dev.springboot.model.Invoice;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.DeleteRequest;
import co.elastic.clients.elasticsearch.core.DeleteResponse;
import co.elastic.clients.elasticsearch.core.GetResponse;
import co.elastic.clients.elasticsearch.core.IndexResponse;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;

@Repository
public class InvoiceRepository {

   @Autowired
   private ElasticsearchClient elasticsearchClient;

   private final String indexName = "invoices";

   public String createOrUpdateInvoice(Invoice invoice) throws IOException {

        IndexResponse response = elasticsearchClient.index(
                       i -> i.index(indexName)
                             .id(invoice.getId())
                             .document(invoice)
        );
        if(response.result().name().equals("Created")){
           return new StringBuilder("Invoice document has been created successfully.").toString();
        }else if(response.result().name().equals("Updated")){
           return new StringBuilder("Invoice document has been updated successfully.").toString();
        }
        return new StringBuilder("Error while performing the operation.").toString();
   }

   public Invoice getInvoiceById(String invoiceId) throws IOException{
      Invoice invoice = null;
      GetResponse<Invoice> response = elasticsearchClient.get(
                           g -> g.index(indexName)
                                 .id(invoiceId),
                                  Invoice.class
      );

      if (response.found()) {
        invoice = response.source();
        System.out.println("Invoice name is: " + invoice.getName());
      } else {
        System.out.println ("Invoice not found");
      }
      return invoice;
   }

   public String deleteInvoiceById(String invoiceId) throws IOException {

      DeleteRequest request = DeleteRequest.of(d -> d.index(indexName).id(invoiceId));

      DeleteResponse deleteResponse = elasticsearchClient.delete(request);
      if (Objects.nonNull(deleteResponse.result()) && !deleteResponse.result().name().equals("NotFound")) {
         return new StringBuilder("Invoice with id : " + deleteResponse.id()+ " has been deleted successfully !.").toString();
      }
      System.out.println("Invoice not found");
         return new StringBuilder("Invoice with id : " + deleteResponse.id()+" does not exist.").toString();
   }

   public List<Invoice> getAllInvoices() throws IOException {

      SearchRequest searchRequest = SearchRequest.of(s -> s.index(indexName));
      SearchResponse<Invoice> searchResponse = elasticsearchClient.search(searchRequest, Invoice.class);
      List<Hit<Invoice>> hits = searchResponse.hits().hits();
      List<Invoice> invoices = new ArrayList<>();
      for(Hit<Invoice> object : hits){ 
         System.out.print(((Invoice) object.source()));
         invoices.add((Invoice) object.source());
      }
      return invoices;
   }
}

Step#5A: Create a RestController class as InvoiceRestController

If you want to create Spring Data Elasticsearch REST CRUD API Using Spring Boot instead of Spring Data Elasticsearch CRUD Examples Using Spring Boot, include this class in your project. In this case, there is no need to create UI part. We can test it with the help of any REST client such as Postman tool.

InvoiceRestCotroller.java

import java.io.IOException;
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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.dev.springboot.model.Invoice;
import com.dev.springboot.repo.InvoiceRepository;

@RestController
public class InvoiceRestController {

   @Autowired
   private InvoiceRepository repo;

   @PostMapping("/createOrUpdateInvoice")
   public ResponseEntity<Object> createOrUpdateInvoice(@RequestBody Invoice invoice) throws IOException {
      String response = repo.createOrUpdateInvoice(invoice);
      return new ResponseEntity<>(response, HttpStatus.OK);
   }

   @GetMapping("/getInvoice")
   public ResponseEntity<Object> getInvoiceById(@RequestParam String invoiceId) throws IOException {
      Invoice invoice = repo.getInvoiceById(invoiceId);
      return new ResponseEntity<>(invoice, HttpStatus.OK);
   }

   @GetMapping("/getAllInvoices")
   public ResponseEntity<Object> getAllInvoices() throws IOException {
      List<Invoice> invoices = repo.getAllInvoices();
      return new ResponseEntity<>(invoices, HttpStatus.OK);
   }

   @DeleteMapping("/deleteInvoice")
   public ResponseEntity<Object> deleteInvoiceById(@RequestParam String invoiceId) throws IOException {
      String response = repo.deleteInvoiceById(invoiceId);
      return new ResponseEntity<>(response, HttpStatus.OK);
   }
}

Step#5B: Create a Controller class as InvoiceController.java

Let’s create a Controller class as InvoiceController.java which will communicate with UI to perform CRUD operations.

InvoiceCotroller.java

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import com.dev.springboot.model.Invoice;
import com.dev.springboot.repo.InvoiceRepository;

@Controller
public class InvoiceController {

   @Autowired
   private InvoiceRepository repo;

   @GetMapping("/")
   public String viewHomePage(Model model){
      return "home";
   }

   @GetMapping("/listInvoice")
   public String viewlistInvoicePage(Model model) throws IOException {
      model.addAttribute("listInvoiceDocuments",repo.getAllInvoices());
      return "listInvoice";
   }

   @PostMapping("/saveInvoice")
   public String saveInvoice(@ModelAttribute("invoice") Invoice invoice) throws IOException {
      repo.createOrUpdateInvoice(invoice);
      return "redirect:/listInvoice";
   }

   @GetMapping("/showFormForUpdate/{id}")
   public String showFormForUpdate(@PathVariable(value = "id") String id, Model model) throws IOException {
      Invoice invoice = repo.getInvoiceById(id);
      model.addAttribute("invoice", invoice);
      return "updateInvoice";
   }

   @GetMapping("/showNewInvoiceForm")
   public String showNewInvoiceForm(Model model) {
      // creating model attribute to bind form data
      Invoice invoice = new Invoice();
      model.addAttribute("invoice", invoice);
      return "addInvoice";
   }

   @GetMapping("/deleteInvoice/{id}")
   public String deleteInvoice(@PathVariable(value = "id") String id) throws IOException {
      this.repo.deleteInvoiceById(id);
      return "redirect:/listInvoice";
   }
}

Step#6: Create pages for view

The last part of our Spring Data Elasticsearch CRUD Examples Using Spring Boot is to create UI pages that will help users to interact with the application. Here, we have four pages : home.html as an entry point of the application, addInvoice.html to fill the Invoice form and register an Invoice, listInvoice.html to see the list of registered invoices, and updateInvoice.html to update the data of any invoice.

home.html

home.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://thymeleaf.org">

<head>
    <meta charset="ISO-8859-1">
    <title>Invoice Page</title>

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
          integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">



</head>

<body>

<div class="container my-5 ">
    <h1 class="text-center">{Spring Boot + Elasticsearch App}<br>Invoice Page<br/>
    </h1>
    <a th:href="@{/showNewInvoiceForm}" class="btn btn-primary m-3"> Add Invoice </a>
<!--     <a href="/listInvoice" class="btn btn-danger text-right">Go To Invoices List</a> -->

</div>
</body>

</html>

addInvoice.html

addInvoice.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://thymeleaf.org">

<head>
    <meta charset="ISO-8859-1">
    <title>Product Store</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
</head>

<body>
<div class="container my-5">
<h1 class="text-center">{Spring Boot + Elasticsearch App}<br>Invoice Page<br/>
     <a href="/" class="btn btn-danger text-right">Go To Home Page</a>
    </h1>
    <hr>
    <h2>Save Invoice</h2>

    <form action="#" th:action="@{/saveInvoice}" th:object="${invoice}" method="POST">
        <input type="text" th:field="*{id}" placeholder="Invoice Id" class="form-control mb-4 col-4">

        <input type="text" th:field="*{name}" placeholder="Invoice Name" class="form-control mb-4 col-4">

        <input type="text" th:field="*{number}" placeholder="Invoice Number" class="form-control mb-4 col-4">

        <input type="text" th:field="*{amount}" placeholder="Invoice Amount" class="form-control mb-4 col-4">

        <button type="submit" class="btn btn-info col-2"> Save Invoice</button>
    </form>

    <hr>

    <a th:href="@{/listInvoice}" class="link-success"> Back to Invoice List</a>
</div>
</body>

</html>

listInvoice.html

listInvoice.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://thymeleaf.org">

<head>
    <meta charset="ISO-8859-1">
    <title>Invoice Page</title>

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
          integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

    <style>
#invoices {
  font-family: Arial, Helvetica, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

#invoices td, #invoices th {
  border: 1px solid #ddd;
  padding: 8px;
}

#invoices tr:nth-child(even){background-color: #f2f2f2;}

#invoices tr:hover {background-color: #ddd;}

#invoices th {
  padding-top: 12px;
  padding-bottom: 12px;
  text-align: left;
  background-color: #04AA6D;
  color: white;
}


    </style>
</head>

<body>

<div class="container my-5 ">
    <h1 class="text-center">{Spring Boot + Elasticsearch App}<br>Invoice Page<br/>
    </h1>
    <a th:href="@{/showNewInvoiceForm}" class="btn btn-primary m-3"> Add Invoice </a>
    <a href="/" class="btn btn-danger text-right">Go To Home Page</a>


    <table id="invoices" border="1" class="table table-striped table-responsive-md">
        <thead class="table-dark">
        <tr>
            <th>Id</th>
            <th>Name</th>
            <th>Number</th>
            <th>Amount</th>
            <th>Action</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="invoiceDocument : ${listInvoiceDocuments}">
            <td th:text="${invoiceDocument.id}"></td>
            <td th:text="${invoiceDocument.name}"></td>
            <td th:text="${invoiceDocument.number}"></td>
            <td th:text="${invoiceDocument.amount}"></td>
            <td><a th:href="@{/showFormForUpdate/{id}(id=${invoiceDocument.id})}" class="btn btn-success">Update</a>
                <a th:href="@{/deleteInvoice/{id}(id=${invoiceDocument.id})}" class="btn btn-danger">Delete</a>
            </td>
        </tr>
        </tbody>
    </table>
</div>
</body>

</html>

updateInvoice.html

updateInvoice.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="ISO-8859-1">
    <title>Product Store</title>

    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
</head>

<body>
<div class="container my-5">
    <h1 class="text-center">{Spring Boot + Elasticsearch App}<br>Invoice Page<br/>
        <a href="/" class="btn btn-danger text-right">Go To Home Page</a>
    </h1>
    <hr>
    <h2>Update Invoice</h2>

    <form action="#" th:action="@{/saveInvoice}" th:object="${invoice}" method="POST">

        <!-- Add hidden form field to handle update -->
        <input type="hidden" th:field="*{id}" />

        <input type="text" th:field="*{name}" placeholder="Invoice Name" class="form-control mb-4 col-4">

        <input type="text" th:field="*{Number}" placeholder="Invoice Number" class="form-control mb-4 col-4">

        <input type="text" th:field="*{Amount}" placeholder="Invoice Amount" class="form-control mb-4 col-4">

        <button type="submit" class="btn btn-info col-2"> Update Invoice</button>
    </form>

    <hr>

    <a th:href="@{/listInvoice}" class="link-success"> Back to Invoice List</a>
</div>
</body>

</html>

At the end, the project structure will look something like below screenshot.

Spring Data Elasticsearch CRUD Examples Using Spring Boot

How to test the application?

In order to test the application, we need to open a browser and hit the URL http://localhost:8080/. On hitting the URL, the home page will get displayed. Further, follow the links and buttons given in the home page accordingly.

Conclusion

In this article we have covered all the theoretical and example part of ‘Spring Data Elasticsearch CRUD examples Using Spring Boot’, finally, you should be able to build an MVC application using Spring Boot and Elasticsearch. Similarly, we expect from you to further extend this example, as per your requirement. Also try to implement it in your project accordingly. Moreover, Feel free to provide your comments in the comments section below.

close

Leave a Reply

Top