栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java | 使用 Hibernate Search 构建一个带有全文搜索的 Spring Boot REST API (二)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java | 使用 Hibernate Search 构建一个带有全文搜索的 Spring Boot REST API (二)

6 定义业务层

现在,让我们声明一个服务,使用刚才定义的数据层创建业务代码。

package com.mozen.springboothibernatesearch.service;

import com.mozen.springboothibernatesearch.model.Plant;
import com.mozen.springboothibernatesearch.repository.PlantRepository;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.List;

@Service
public class PlantService {

    private PlantRepository plantRepository;

    private static final List SEARCHABLE_FIELDS = Arrays.asList("name","scientificName","family");

    public PlantService(PlantRepository plantRepository) {
        this.plantRepository = plantRepository;
    }

    public List searchPlants(String text, List fields, int limit) {

        List fieldsToSearchBy = fields.isEmpty() ? SEARCHABLE_FIELDS : fields;

        boolean containsInvalidField = fieldsToSearchBy.stream(). anyMatch(f -> !SEARCHABLE_FIELDS.contains(f));

        if(containsInvalidField) {
            throw new IllegalArgumentException();
        }

        return plantRepository.searchBy(
                text, limit, fieldsToSearchBy.toArray(new String[0]));
    }
}

我们告诉 Spring 这个 bean 是业务层的一部分。

它包含一个 searchPlant 函数,该函数将调用转发给 SearchRepository 的 searchBy 函数。

在转发调用之前,要验证提供的字段。

这些字段是白名单,以检查搜索是否只针对所需的字段进行,这些字段是我们前面用@FullTextField 注释的字段。

如果提供的字段之一不是白名单字段的一部分,则抛出 llegalArgumentException。为了简单起见,不处理异常,但是应该使用 spring 提供的处理异常的多种方法之一来正确处理异常。

7 定义网络层

下一步是定义 REST API来接收来自客户端应用程序的 HTTP 请求。

package com.mozen.springboothibernatesearch.controller;

import com.mozen.springboothibernatesearch.model.Plant;
import com.mozen.springboothibernatesearch.model.SearchRequestDTO;
import com.mozen.springboothibernatesearch.service.PlantService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Slf4j
@RestController
@RequestMapping("/plant")
public class PlantController {

    private PlantService plantService;

    public PlantController(PlantService plantService) {
        this.plantService = plantService;
    }

    @GetMapping("/search")
    public List searchPlants(SearchRequestDTO searchRequestDTO) {

        log.info("Request for plant search received with data : " + searchRequestDTO);

        return plantService.searchPlants(searchRequestDTO.getText(), searchRequestDTO.getFields(), searchRequestDTO.getLimit());
    }
}

我们使用带有单个 GET 映射的基本 Rest 控制器。在将调用转发到业务层之前,我们记录事件以跟踪请求的接收情况,从而简化对应用程序的监视。它使用 SearchRequestDTO 接收搜索请求。

package com.mozen.springboothibernatesearch.model;

import lombok.Data;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import java.util.ArrayList;
import java.util.List;

@Data
public class SearchRequestDTO {

    @NotBlank
    private String text;

    private List fields = new ArrayList<>();

    @Min(1)
    private int limit;
}

着是一个简单的 POJO,包含用于搜索的参数。同样,我们使用 javaxbean 验证注释来确保请求是有效的,并使用龙目岛@Data 注释来生成样板代码(getter、 setter、 toString ()、 …)。

注意,通过使用 POJO 作为 REST API 端点的单个参数,我们期望客户机将这些参数作为请求参数发送到 HTTP 请求中。

8 索引数据

最后,为了让 Lucene 能够搜索数据,需要对它进行索引。

在运行时,该索引由 Hibernate 自动管理,通过 Hibernate ORM 在每次执行操作(如创建或删除一个实体)时进行更改。但是,我们仍然需要初始化已经存储在数据库中的数据的索引(如果有的话)。

为此,我们需要在 application.yml 文件中添加一些配置:

server:
    port: 9000

spring:
    datasource:
        url: jdbc:h2:mem:mydb
        username: mozen
        password: password
    jpa:
        open-in-view: false
        properties:
            hibernate:
                search:
                    backend:
                        type: lucene
                        directory.root: ./data/index

我们指明存储 Lucene 索引的根目录。在这里,我们选择将它直接放在项目文件夹中,但是在生产环境中运行时,应该谨慎选择这个目录,这取决于您的应用程序部署在哪里。

我们还创建了一个组件来封装与 Lucene 索引相关的所有操作。

package com.mozen.springboothibernatesearch.index;

import org.hibernate.search.mapper.orm.Search;
import org.hibernate.search.mapper.orm.massindexing.MassIndexer;
import org.hibernate.search.mapper.orm.session.SearchSession;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;

@Transactional
@Component
public class Indexer {

    private EntityManager entityManager;

    private static final int THREAD_NUMBER = 4;

    public Indexer(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public void indexPersistedData(String indexClassName) throws IndexException {

        try {
            SearchSession searchSession = Search.session(entityManager);

            Class classToIndex = Class.forName(indexClassName);
            MassIndexer indexer =
                    searchSession
                            .massIndexer(classToIndex)
                            .threadsToLoadObjects(THREAD_NUMBER);

            indexer.startAndWait();
        } catch (ClassNotFoundException e) {
            throw new IndexException("Invalid class " + indexClassName, e);
        } catch (InterruptedException e) {
            throw new IndexException("Index Interrupted", e);
        }
    }
}

对于我们的简单演示应用程序,我们只声明一个函数,该函数使用进程中指定数量的线程为给定的 Class 构建索引。

现在,我们需要调用这个函数,将以前定义的 Plant 类作为参数传递给它。

为此,我们还可以创建一个新的 REST Controler,其中包含一个通过 HTTP 请求触发索引的端点,以便能够随意重建索引。

我们只需要使用一个 ApplicationRunner,它将在每次启动时被调用。

@SpringBootApplication
public class Application {

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

	@Bean
	public ApplicationRunner buildIndex(Indexer indexer) throws Exception {
		return (ApplicationArguments args) -> {
			indexer.indexPersistedData("com.mozen.springboothibernatesearch.model.Plant");
		};
	}
}

我们直接在主 Application 类中定义 Bean。

方法使用 Indexer 作为参数通过依赖注入。该方法在应用程序启动期间、上下文初始化之后以及在 spring 引导应用程序启动之前执行。

9 把它们放在一起

让我们首先初始化示例数据。

package com.mozen.springboothibernatesearch;

import com.mozen.springboothibernatesearch.index.Indexer;
import com.mozen.springboothibernatesearch.model.Plant;
import com.mozen.springboothibernatesearch.repository.PlantRepository;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

import java.util.Arrays;
import java.util.List;

@SpringBootApplication
public class Application {

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

	@Bean
	public ApplicationRunner initializeData(PlantRepository plantRepository) throws Exception {
		return (ApplicationArguments args) -> {
			List plants = Arrays.asList(
					new Plant("subalpine fir", "abies lasiocarpa", "pinaceae"),
					new Plant("sour cherry", "prunus cerasus", "rosaceae"),
					new Plant("asian pear", "pyrus pyrifolia", "rosaceae"),
					new Plant("chinese witch hazel", "hamamelis mollis", "hamamelidaceae"),
					new Plant("silver maple", "acer saccharinum", "sapindaceae"),
					new Plant("cucumber tree", "magnolia acuminata", "magnoliaceae"),
					new Plant("korean rhododendron", "rhododendron mucronulatum", "ericaceae"),
					new Plant("water lettuce", "pistia", "araceae"),
					new Plant("sessile oak", "quercus petraea", "fagaceae"),
					new Plant("common fig", "ficus carica", "moraceae")
			);
			plantRepository.saveAll(plants);
		};
	}

	@Bean
	public ApplicationRunner buildIndex(Indexer indexer) throws Exception {
		return (ApplicationArguments args) -> {
			indexer.indexPersistedData("com.mozen.springboothibernatesearch.model.Plant");
		};
	}

我们用第二个 Bean 声明扩展了主 Application 类。在应用程序启动期间,它将以与第一个应用程序相同的方式运行。

注意,我们没有在任何地方声明数据 SQL 模式。因为我们的数据库是一个嵌入式数据库,所以属性 spring.jpa.hibernate.ddl-auto 被默认设置为 create-drop,并且我们的数据库模式是自动生成的,这对于像这样的简单应用程序来说非常简洁。

现在,让我们通过首先启动应用程序来测试。

mvn spring-boot:run
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/959223.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号