Tuesday, May 9, 2023

Spring Boot 3 Spring Data Elasticsearch

 

Elasticsearch: 

By official documentation Elasticsearch is a distributed, free and open search and analytics engine for all types of data, including textual, numerical, geospatial, structured, and unstructured. Elasticsearch is built on Apache Lucene and was first released in 2010 by Elasticsearch N.V. (now known as Elastic).



Query DSL ?

Query DSL stands for Query Domain-Specific Language, which is a powerful feature of Elasticsearch that enables users to specify search queries in a flexible and intuitive way. It's a JSON-based language that allows you to define complex search queries with a simple and concise syntax.

Query DSL allows you to search for specific documents in your Elasticsearch index based on a wide range of criteria, such as keyword match, ranges of values, geographic locations, and more. You can also use Query DSL to apply filters to your search results and to specify sorting and aggregations.

Technology


  • Spring Boot 3.0.6
  • Docker
  • Maven 
  • Elasticsearch 7.17.5
  • IntelliJ IDEA

Configuration Spring Boot project  :


<?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>3.0.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.henry</groupId>
<artifactId>SpringDataElasticsearchQueryDsl</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringDataElasticsearchQueryDsl</name>
<description>Demo Spring boot + elasticsearch + querydsl</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</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.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>


application.yml:


server:
port: 9000
servlet:
context-path: /

elasticsearch:
host: localhost
port: 9200




Spring Data ElasticSearch Application

Configuration


package com.henry.configuration;

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.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticsearchConfig {

@Value("${elasticsearch.host}")
private String EsHost;

@Value("${elasticsearch.port}")
private int EsPort;

@Bean
public ElasticsearchTransport client() {

// Create the low-level client
RestClient restClient = RestClient.builder(
new HttpHost(EsHost, EsPort)).build();

// Create the transport with a Jackson mapper
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper());

return transport;
}

// And create the API client
@Bean
public ElasticsearchClient elasticsearchClient() {
return new ElasticsearchClient(client());
}
}


Model


package com.henry.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.elasticsearch.annotations.Document;

@Document(indexName = "users")
public class
User {

@Id
private Long id;
private String name;
private String lastName;

@Transient
private String _class;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String get_class() {
return _class;
}

public void set_class(String _class) {
this._class = _class;
}
}



Repository


package com.henry.repository;

import com.henry.model.User;
import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User,Long> {

Iterable<User> findByName(String name);
}


Service


package com.henry.service;

import com.henry.model.User;

public sealed interface UserService permits UserServiceImpl {

User save(User user);

User update(Long id, User user);

void delete(Long id);

User findOne(Long id);

Iterable<User> findAll();

Iterable<User> findByName(String name);

}


ServiceImpl


package com.henry.service;

import com.henry.model.User;
import com.henry.repository.UserRepository;
import org.springframework.stereotype.Service;

@Service
public final class UserServiceImpl implements UserService {

private final UserRepository userRepository;

public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}

@Override
public User save(User user) {
return userRepository.save(user);
}

@Override
public User update(Long id, User user) {
var obj = userRepository.findById(id);
obj.get().setName(user.getName());
obj.get().setLastName(user.getLastName());
return userRepository.save(obj.get());
}

@Override
public void delete(Long id) {
userRepository.deleteById(id);
}

@Override
public User findOne(Long id) {
return userRepository.findById(id).get();
}

@Override
public Iterable<User> findAll() {
return userRepository.findAll();
}

@Override
public Iterable<User> findByName(String name) {
return userRepository.findByName(name);
}
}


Controller


package com.henry.controller;

import com.henry.model.User;
import com.henry.service.UserService;
import org.springframework.web.bind.annotation.*;


@RestController
@RequestMapping("/users")
public class UserController {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@PostMapping("/save")
public User add(@RequestBody User user) {
return userService.save(user);
}

@PutMapping("/update/{id}")
public User update(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}

@GetMapping("/findOne/{id}")
public User findOne(@PathVariable Long id) {
return userService.findOne(id);
}

@GetMapping("/all")
public Iterable<User> findAll() {
return userService.findAll();
}

@GetMapping("/findByName/{name}")
public Iterable<User> findByName(@PathVariable String name) {
return userService.findByName(name);
}

@DeleteMapping("/delete/{id}")
public void delete(@PathVariable Long id) {
userService.delete(id);
}

}


Resource


package com.henry.resource;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.henry.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

@Component
public class SearchQueryBuilder {

@Autowired
private ElasticsearchClient client;

public List<User> getByName(String text) throws IOException {
var list = new ArrayList<User>();
SearchResponse<User> search = client.search(s -> s
.index("users")
.query(q -> q
.term(t -> t
.field("name")
.value(v -> v.stringValue(text))
)),
User.class);

for (Hit<User> hit: search.hits().hits()) {
list.add(hit.source());
}

return list;
}

}

package com.henry.resource;

import com.henry.model.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@RequestMapping("/user-resource")
public class UserResource {

private final SearchQueryBuilder searchQueryBuilder;

public UserResource(SearchQueryBuilder searchQueryBuilder) {
this.searchQueryBuilder = searchQueryBuilder;
}

@GetMapping("/{name}")
public List<User> getByName(@PathVariable String name) throws IOException {
return searchQueryBuilder.getByName(name);
}
}

Downloading and installing Elasticsearch

docker run -d --name elasticsearch -p 9200:9200 -e "discovery.type=single-node" elasticsearch:7.17.5


Run & Test


Run Spring Boot application with command: mvn spring-boot:run. by console, IntelliJ etc.

POST
http://localhost:9000/users/save
















GET
http://localhost:9000/user-resource/henry

















GET
http://localhost:9200/_search?q=Henry
















GET
http://localhost:9000/users/findByName/henry


















Source Code


Here on GitHub.



References.

https://www.elastic.co/what-is/elasticsearch
https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/connecting.html








No comments:

Post a Comment

Multiple Data Sources in Spring Boot 3 with Java 21

  In this blog post, we'll explore the configuration and setup for a Spring Boot 3 application with Java 21 that uses multiple data sour...