Open In App

Spring Boot – Build a Dynamic Full Text Search API Using JPA Queries

A Global Full-Text Entity Search is a search functionality that allows users to find entities such as records or objects within a dataset by searching through the entirety of their content. Global Full-Text Entity Search is like having a super-smart search companion that digs deep into the entire content of entities. Consider the below examples to understand this better.

Examples for Building Text API

1). GeeksforGeeks Website Example

2). Library Example

3). Restaurant Example

These examples illustrate how a Global Full-Text Entity Search simplifies the process of finding specific information in a large dataset, whether it’s a library of books or a restaurant menu.



Implementing REST API for Entity Global Search

Now we are going to implement global search API for Article Entity.Below steps to be followed while creating a spring boot REST API for entity global search.

Step 1: Set Up a Spring Boot Project




<?xml version="1.0" encoding="UTF-8"?>
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.1</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>FullTextSearchApi</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>FullTextSearchApi</name>
    <description>RESTful API for global full-text entity search</description>
    <properties>
        <java.version>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>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.8.1</version>
            <scope>test</scope>
        </dependency>
        <!-- Spring Boot Test -->
        <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>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
  
</project>



Step 2: Define the Article Entity

Create an Article class representing the entity you want to search. Include relevant attributes such as id, title, content, etc.




package com.example.demo.entity;
  
import java.time.LocalDate;
import java.util.UUID;
  
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;
  
/**
 * Entity class representing an article.
 */
@Entity
@Table(name = "articles")
@Data
public class Article {
  
    /**
     * Unique identifier for the article.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
  
    /**
     * Title of the article.
     */
    @Column(name = "title")
    private String title;
  
    /**
     * Author of the article.
     */
    @Column(name = "author")
    private String author;
  
    /**
     * Content or body of the article.
     */
    @Column(name = "content")
    private String content;
  
    /**
     * Date when the article was published.
     */
    @Column(name = "publication_date")
    private LocalDate publicationDate;
  
    /**
     * Category or topic of the article.
     */
    @Column(name = "category")
    private String category;
  
    /**
     * Keywords associated with the article.
     */
    @Column(name = "keywords")
    private String keywords;
}

where,

Step 3: Set Up Repository

Create a repository interface for the Article entity to interact with the database.




package com.example.demo.repository;
  
import com.example.demo.entity.Article;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
  
import java.util.List;
import java.util.UUID;
  
public interface ArticleRepository extends JpaRepository<Article, UUID> {
  
    @Query("SELECT a FROM Article a WHERE " +
            "LOWER(a.content) LIKE LOWER(CONCAT('%', :searchText, '%')) OR " +
            "LOWER(a.title) LIKE LOWER(CONCAT('%', :searchText, '%')) OR " +
            "LOWER(a.author) LIKE LOWER(CONCAT('%', :searchText, '%')) OR " +
            "LOWER(a.category) LIKE LOWER(CONCAT('%', :searchText, '%')) OR " +
            "LOWER(a.keywords) LIKE LOWER(CONCAT('%', :searchText, '%'))")
    List<Article> findArticlesBySearchText(@Param("searchText") String searchText);
  
    List<Article> findByAuthor(String author);
  
    List<Article> findByTitle(String title);
  
    List<Article> findByCategory(String category);
}

Step 4: Implement Service Interface

Create a service interface (ArticleService) that declares the operations to be performed on articles.




package com.example.demo.service;
  
import com.example.demo.entity.Article;
  
import java.util.List;
import java.util.UUID;
  
public interface ArticleService {
    List<Article> getAllArticles();
    Article getArticleById(UUID id);
    Article createArticle(Article article);
    Article updateArticle(UUID id, Article article);
    void deleteArticle(UUID id);
    List<Article> searchArticles(String searchText);
}

Step 5: Implement Service Class

Create a service class (ArticleServiceImpl) that implements the ArticleService interface.




// ArticleServiceImpl.java
package com.example.demo.service.impl;
  
import com.example.demo.entity.Article;
import com.example.demo.repository.ArticleRepository;
import com.example.demo.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
  
import java.util.List;
import java.util.Optional;
import java.util.UUID;
  
@Service
public class ArticleServiceImpl implements ArticleService {
  
    @Autowired
    private ArticleRepository articleRepository;
  
    @Override
    public List<Article> getAllArticles() {
        return articleRepository.findAll();
    }
  
    @Override
    public Article getArticleById(UUID id) {
        return articleRepository.findById(id).orElse(null);
    }
  
    @Override
    public Article createArticle(Article article) {
        return articleRepository.save(article);
    }
  
    @Override
    public Article updateArticle(UUID id, Article article) {
        Optional<Article> existingArticleOptional = articleRepository.findById(id);
        if (existingArticleOptional.isPresent()) {
            Article existingArticle = existingArticleOptional.get();
            existingArticle.setTitle(article.getTitle());
            existingArticle.setAuthor(article.getAuthor());
            existingArticle.setContent(article.getContent());
            existingArticle.setPublicationDate(article.getPublicationDate());
            existingArticle.setCategory(article.getCategory());
            existingArticle.setKeywords(article.getKeywords());
            return articleRepository.save(existingArticle);
        } else {
            return null;
        }
    }
  
    @Override
    public void deleteArticle(UUID id) {
        articleRepository.deleteById(id);
    }
  
    @Override
    public List<Article> searchArticles(String searchText) {
        return articleRepository.findArticlesBySearchText(searchText);
    }
}

Step 6: Create Controller for REST API

Implement a controller class (ArticleController) that handles HTTP requests for your REST API.




// ArticleController.java
package com.example.demo.controller;
  
import com.example.demo.entity.Article;
import com.example.demo.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
  
import java.util.List;
import java.util.UUID;
  
@RestController
@RequestMapping("/api/articles")
public class ArticleController {
  
    @Autowired
    private ArticleService articleService;
  
    @GetMapping
    public List<Article> getAllArticles() {
        return articleService.getAllArticles();
    }
  
    @GetMapping("/{id}")
    public Article getArticleById(@PathVariable String id) {
        UUID uuid = UUID.fromString(id);
        return articleService.getArticleById(uuid);
    }
  
    @PostMapping
    public Article createArticle(@RequestBody Article article) {
        return articleService.createArticle(article);
    }
  
    @PutMapping("/{id}")
    public Article updateArticle(@PathVariable String id, @RequestBody Article article) {
        UUID uuid = UUID.fromString(id);
        return articleService.updateArticle(uuid, article);
    }
  
    @DeleteMapping("/{id}")
    public void deleteArticle(@PathVariable String id) {
        UUID uuid = UUID.fromString(id);
        articleService.deleteArticle(uuid);
    }
  
    @GetMapping("/search")
    public ResponseEntity<List<Article>> searchArticles(@RequestParam String searchText) {
        List<Article> foundArticles = articleService.searchArticles(searchText);
        if (!foundArticles.isEmpty()) {
            return ResponseEntity.ok(foundArticles);
        } else {
            return ResponseEntity.noContent().build();
        }
    }
}

Step 7: Configure application.properties

Open the src/main/resources/application.properties file and configure your application properties:

# DataSource settings
spring.datasource.url=jdbc:h2:mem:testdb # JDBC URL for the H2 in-memory database
spring.datasource.driverClassName=org.h2.Driver # JDBC driver class for H2
spring.datasource.username=sa # Username for connecting to the database
spring.datasource.password=password # Password for connecting to the database
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect # Hibernate dialect for H2

# H2 Console settings
spring.h2.console.enabled=true # Enable the H2 console
spring.h2.console.path=/h2-console # URL path for accessing the H2 console

# Hibernate settings
spring.jpa.hibernate.ddl-auto=update # Automatically update the database schema based on JPA entity classes
spring.jpa.show-sql=true # Show SQL statements in the console
spring.jpa.properties.hibernate.format_sql=true # Format SQL statements for better readability

# Server port
server.port=8080 # Port on which the embedded Tomcat server will run

POSTMAN Testing Process for Article API Endpoints

Let’s walk through the steps to create an article and then search for it using the searchArticles endpoint.

Create a New Article

For example:

{
"title": "Sample Article",
"author": "John Doe",
"content": "This is a sample article content.",
"publicationDate": "2023-12-25",
"category": "Sample Category",
"keywords": "sample, article"
}

Click on the “Send” button to create the new article. Consider below images.

POSTMAN Request and response:

creating an article

Database Record:

Search for the Created Article:

Set up a new request in Postman:Method: GETURL: http://localhost:8080/api/articles/searchParams: Add a key-value pair:Key: searchTextValue: Enter a keyword that matches the article you created (e.g., Sample).Consider below images:

1). When something is found with the searchText
2). When nothing matched with the given searchText value

Conclusion

We initiated by creating and validating article details in the database. Once ensured, our focus shifted to the search endpoint. If the searchText matched any attribute in our stored articles, a list of matches was promptly delivered. In the absence of matches, a graceful 204 No Content response reassured the absence of results. This concise journey affirms the seamless interplay of article creation and effective global search in our Java and Spring Boot ecosystem.


Article Tags :