ElasticSearch常规操作

导入数据到索引库

0.学习目标

  • 理解索引库数据结构设计
  • 能够把商品数据导入到索引库
  • 能实现搜索补全功能
  • 能实现基本搜索功能

1.ElasticSearch的starter(了解)

ElasticSearch的异步API代码编写比较复杂,因此我们需要封装成工具,方便后期的使用。而且最好与SpringBoot整合,完成自动配置的starter。最终达成与MybatisPlus一样的效果:

  • 用户定义接口并继承我们的接口,获取CRUD的方法
  • 我们实现接口中的方法
  • 通过动态代理来代理用户接口,并注入Spring容器
  • 实现自动配置

我们大概会经过这样几个步骤来完成:

  • 定义工具类
  • 与spring整合
  • 形成starter

1.1.搭建工程

SpringBoot的starter结构一般如下:

  • spring-boot-xxx-autoconfigure:实现自动配置的代码、工具类、配置类等
  • spring-boot-starter-xxx:管理spring-boot-xxx-autoconfigure及其它所需依赖的版本

因此我们如果要自定义工具类,并且实现starter,项目结构也是如此,包含:

  • leyou-starters:父工程,起聚合作用,方便开发
    • elastic-spring-boot-autoconfigure:工具类、配置类等
    • elastic-spring-boot-starter:引入spring-boot-xxx-autoconfigure及其它所需依赖

1.1.1.创建父工程:

创建一个普通maven工程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ENqnXVyj-1614752234688)(assets/image-20200517111029233.png)]

位置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YLDWSCJd-1614752234690)(assets/image-20200517111050389.png)]

pom:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou</groupId>
    <artifactId>leyou-starters</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <packaging>pom</packaging>

    <properties>
        <spring.boot.version>2.1.12.RELEASE</spring.boot.version>
        <elasticsearch.version>7.4.2</elasticsearch.version>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

1.1.2.AutoConfigure

然后是搭建AutoConfigure模块。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QVlWOUc2-1614752234691)(assets/image-20200517111202030.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Izpl0io-1614752234693)(assets/image-20200517111215955.png)]

坐标:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MCt1aX7A-1614752234694)(assets/image-20200517111222004.png)]

依赖:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou-starters</artifactId>
        <groupId>com.leyou</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>elastic-spring-boot-autoconfigure</artifactId>

    <dependencies>
        <!--springboot 自动配置基本依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>${spring.boot.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <version>${spring.boot.version}</version>
            <optional>true</optional>
        </dependency>
        <!--elasticsearch依赖-->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>${elasticsearch.version}</version>
            <scope>compile</scope>
        </dependency>
        <!--JSON依赖-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.10.3</version>
            <scope>compile</scope>
        </dependency>
        <!--通用模块-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
            <scope>compile</scope>
        </dependency>
        <!--响应式API 依赖-->
        <dependency>
            <groupId>io.projectreactor.netty</groupId>
            <artifactId>reactor-netty</artifactId>
            <version>0.8.15.RELEASE</version>
            <scope>compile</scope>
        </dependency>
        <!--日志相关-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
            <version>2.11.2</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jul-to-slf4j</artifactId>
            <version>1.7.30</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

1.1.3.Starter模块

然后是starter模块,与AutoConfigure模块一样,是一个普通maven模块

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ScgyYhbP-1614752234695)(assets/image-20200517111258427.png)]

依赖如下:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou-starters</artifactId>
        <groupId>com.leyou</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>elastic-spring-boot-starter</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.leyou</groupId>
            <artifactId>elastic-spring-boot-autoconfigure</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
    </dependencies>
</project>

1.2.定义工具类

课程中为了时间考虑,我们只把核心需要的几个功能封装,并不追求功能的完整性,包括下列方法:

  • 创建索引库和映射
  • 删除索引库
  • 新增文档
  • 查询文档
  • 删除文档
  • 搜索并实现分页、高亮、排序
  • 自动补全查询

因为包含分页查询,因此需要一个DTO,代表分页结果,

我们在com.leyou.starter.elastic.entity包下定义一个类:

package com.leyou.starter.elastic.entity;

import java.util.List;


public class PageInfo<T> {
   
   
    private long total;
    private List<T> content;

    public PageInfo() {
   
   
    }

    public PageInfo(long total, List<T> content) {
   
   
        this.total = total;
        this.content = content;
    }

    public long getTotal() {
   
   
        return total;
    }

    public void setTotal(long total) {
   
   
        this.total = total;
    }

    public List<T> getContent() {
   
   
        return content;
    }

    public void setContent(List<T> content) {
   
   
        this.content = content;
    }
}

1.2.1.定义工具接口

首先我们在com.leyou.starter.elastic.repository包中定义一个接口,声明需要的方法:

package com.leyou.starter.elastic.repository;

import com.leyou.starter.elastic.dto.PageInfo;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 定义了操作Elasticsearch的CRUD的功能 <br/>
 * 泛型说明 <br/>
 * T:实体类类型
 * ID:实体类中的id类型
 */
public interface Repository<T, ID> {
   
   
    /**
     * 创建索引库
     *
     * @param source setting和mapping的json字符串
     * @return 是否创建成功
     */
    Boolean createIndex(String source);

    /**
     * 删除当前实体类相关的索引库
     *
     * @return 是否删除成功
     */
    Boolean deleteIndex();

    /**
     * 新增数据
     *
     * @param t 要新增的数据
     * @return 是否新增成功
     */
    boolean save(T t);

    /**
     * 批量新增
     *
     * @param iterable 要新增的数据结婚
     * @return 是否新增成功
     */
    boolean saveAll(Iterable<T> iterable);

    /**
     * 根据id删除数据
     *
     * @param id id
     * @return 是否删除成功
     */
    boolean deleteById(ID id);

    /**
     * 异步功能,根据id查询数据
     *
     * @param id id
     * @return 包含实体类的Mono实例
     */
    Mono<T> queryById(ID id);


    /**
     * 根据{@link SearchSourceBuilder}查询数据,返回分页结果{@link PageInfo},其中的数据已经高亮处理
     *
     * @param sourceBuilder 查询条件构建器
     * @return 结果处理器处理后的的数据
     */
    Mono<PageInfo<T>> queryBySourceBuilderForPageHighlight(SearchSourceBuilder sourceBuilder);

    /**
     * 根据指定的prefixKey对单个指定suggestField 做自动补全,返回推荐结果的列表{@link List}
     * @param suggestField 补全字段
     * @param prefixKey 关键字
     * @return 返回推荐结果列表{@link List}
     */
    Mono<List<String>> suggestBySingleField(String suggestField, String prefixKey);
}

1.2.2.定义实现类

我们在com.leyou.starter.elastic.repository包中定义实现类,暂时不实现代码:

package com.leyou.starter.elastic.repository;



public class RepositoryHandler<T, ID> implements Repository<T, ID> {
   
   

    /**
     * Elasticsearch的客户端
     */
    private final RestHighLevelClient client;
    /**
     * 索引库名称
     */
    private String indexName;

    public RepositoryHandler(RestHighLevelClient client, String indexName){
   
   
        this.client = client;
        this.indexName = indexName;
    }

    // ...
}

解读:

通过构造函数我们注入了两个属性:

  • RestHighLevelClient client:操作elasticsearch需要用到的客户端
  • String indexName:要操作的索引库名称

1.2.3.创建和删除索引库

先来实现索引库操作:

@Override
public Boolean createIndex(String source) {
   
   
    try {
   
   
        // 发起请求,准备创建索引库
        CreateIndexResponse response = client.indices().create(
            new CreateIndexRequest(indexName).source(source, XContentType.JSON),
            RequestOptions.DEFAULT);
        // 返回执行结果
        return response.isAcknowledged();
    } catch (IOException e) {
   
   
        throw new RuntimeException(e);
    }
}

@Override
public Boolean deleteIndex() {
   
   
    try {
   
   
        // 发起请求,删除索引库
        AcknowledgedResponse response = client.indices()
            .delete(new DeleteIndexRequest(indexName), RequestOptions.DEFAULT);
        // 返回执行结果
        return response.isAcknowledged();
    } catch (IOException e) {
   
   
        throw new RuntimeException(e);
    }
}

这两个都是同步操作,没有调用异步的API。

1.2.4.新增文档

新增文档代码如下:

@Override
public boolean save(T t) {
   
   
    try {
   
   
        // 从对象中获取id
        String id = getID(t);
        // 把对象转为JSON
        String json = toJson(t);
        // 准备请求
        IndexRequest request = new IndexRequest(indexName)
            .id(id)
            .source(json, XContentType.JSON);
        // 发出请求
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        // 判断是否有失败
        return response.getShardInfo().getFailed() == 0;
    } catch (IOException e) {
   
   
        throw new RuntimeException(e);
    }
}

注意,新增的时候需要做两件事情:

  • 获取文档数据中的ID属性:这里通过一个getID()方法来获取,这个方法暂时空实现

    private String getID(T t) {
         
         
        // TODO 待完成
    	return "";
    }
    
  • 将文档转为JSON格式:这里通过Jackson的ObjectMapper来封装方法toJson()

    private static final ObjectMapper mapper = new ObjectMapper();
    
    private String toJson(Object o) {
         
         
        try {
         
         
            return mapper.writeValueAsString(o);
        } catch (JsonProcessingException e) {
         
         
            throw new RuntimeException(e);
        }
    }
    
    private T fromJson(String json) {
         
         
        try {
         
         
            return mapper.readValue(json, clazz);
        } catch (IOException e) {
         
         
            throw new RuntimeException(e);
        }
    }
    

1.2.5.批量新增

批量新增时利用BulkRequest,把多个IndexRequest封装,然后一次请求中发出,代码如下:

@Override
public boolean saveAll(Iterable<T> iterable) {
   
   
    // 创建批处理请求
    BulkRequest request = new BulkRequest();
    // 遍历要处理的文档集合,然后创建成IndexRequest,逐个添加到BulkRequest中
    iterable.forEach(t -> request.add(new IndexRequest(indexName).id(getID(t)).source(toJson(t), XContentType.JSON)));
    try {
   
   
        // 发送批处理请求
        BulkResponse bulkResponse = client.bulk(request, RequestOptions.DEFAULT);
        // 判断结果
        if(bulkResponse.status() != RestStatus.OK){
   
   
            return false;
        }
        if(bulkResponse.hasFailures()){
   
   
            throw new RuntimeException(bulkResponse.buildFailureMessage());
        }
        return true;
    } catch (IOException e) {
   
   
        throw new RuntimeException(e);
    }
}

1.2.6.删除文档

这里采用根据id删除文档的方式:

@Override
public boolean deleteById(ID id) {
   
   
    try {
   
   
        // 准备请求
        DeleteRequest request = new DeleteRequest(indexName, id.toString());
        // 发出请求
        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
        // 判断是否有失败
        return response.getShardInfo().getFailed() == 0;
    } catch (IOException e) {
   
   
        throw new RuntimeException(e);
    }
}

1.2.7.根据id查询文档

查询业务采用异步操作,并将结果用Mono封装:

@Override
public Mono<T> queryById(ID id) {
   
   
    // 通过Mono.create函数来构建一个Mono,sink用来发布查询到的数据或失败结果
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值