导入数据到索引库
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用来发布查询到的数据或失败结果

最低0.47元/天 解锁文章
319

被折叠的 条评论
为什么被折叠?



