前言
ElasticSearch6.8.4的实战练习记录 《诗词搜索ver 1.0》
适合有一定ES基础的同学,只说HighLevelClient的功能实现。
环境配置:Docker(ElasticSearch6.8.4+Kibana6.8.4+IK分词器)+SpringBoot 2.2.13.RELEASE + MySQL5.7 + MyBatis + Redis(未使用,后续添加热点词汇记录,从而控制扩展词典和禁用词典)
展示
源码
如何运行?
在application.yml中修改数据库账号密码
创建es数据库,在其中创建poem表
create table poem
(
id bigint auto_increment
primary key,
no varchar(255) not null,
title varchar(100) not null,
author varchar(10) not null,
content varchar(500) not null,
type tinyint default 0 null comment '诗歌类型 四言绝句 五言绝句',
author_detail varchar(500) not null,
source varchar(50) default '网络' not null comment '来源',
create_time datetime null,
update_time datetime null,
constraint no
unique (no)
)
comment '诗' charset = utf8mb4;
配置elasticSearch地址
配置索引
PUT /es
{
"mappings": {
"poem":{
"properties":{
"id":{
"type":"keyword"
},
"no":{
"type":"keyword"
},
"title":{
"type":"text",
"analyzer":"ik_max_word"
},
"author":{
"type":"text",
"analyzer":"ik_max_word"
},
"content":{
"type":"text",
"analyzer":"ik_max_word"
},
"type":{
"type":"integer"
},
"authorDetail":{
"type":"text",
"analyzer":"ik_max_word"
},
"source":{
"type":"keyword"
},
"createTime":{
"type":"date",
"format":"yyyy-MM-dd HH:mm:ss"
},
"updateTime":{
"type":"date",
"format":"yyyy-MM-dd HH:mm:ss"
}
}
}
}
}
启动前端,npm run serve
RestHighLevelClient操作
学习ElasticSearch的时候,ElasticSearch升级挺快的,在网上也搜索不到什么好文章,学RestHighLevelClient的操作一头雾水。好在官网文档还算详尽,果然还得是官方文档最好了,虽然不够综合,但是慢慢磨也能下来。所以
推荐直接看官方英文文档。
推荐直接看官方英文文档。
推荐直接看官方英文文档。
重要的事情说三遍,不管你是什么版本6.x还是7.x,不要在网上找其他文章(只能当作综合方法的参考)!
怕有同学找不到,我就直接帮大家贴在下面,注意版本一致。
ElasticSearch官方Rest Client文档(英文)
生成所有数据库中的数据的ES文档
这个功能第一反应是ES的批处理方法,我们来看ES的官方文档。
当然我们也可以在批处理请求中添加其他请求
官网中还有其他可配置信息以及如何同步(异步)请求,根据自己需要翻阅吧。
这里使用同步请求,首先获取数据库中的全部数据(用作案例,勿喷),然后根据数据库数据生成新的索引请求并加入批处理中去。随后用highLevelClient发起请求,获取到批处理的返回数据bulkResponse,并进行处理。
代码:
/**
* 生成所有数据库中数据的es文档
*/
@Override
public void insertEsAllDate() throws IOException {
List<Poem> poems = poemMapper.queryAll();
BulkRequest bulkRequest = new BulkRequest();
for (Poem poem : poems) {
String s = JSON.toJSONString(poem);
bulkRequest.add(new IndexRequest("es","poem",poem.getNo()).source(s, XContentType.JSON));
}
BulkResponse bulkResponse = highLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
if (bulkResponse.hasFailures()){
for (BulkItemResponse item : bulkResponse.getItems()) {
System.out.println(item.getFailureMessage());
}
}
}
清空ES中的所有文档
查看文档我们可以发现有一个delete-by-query操作。从字面意思上来说就是根据查询到的删除。那么如果我们要实现清空文档的操作,那么只需要查询所有,再根据查询删除即可。
那么我们如何获得查询所有文档,查看官方的搜索文档就可以发现已经给出演示。
这里和文档略有差异。道理一样。
代码:
/**
* 清空es中`es`索引的表
*/
@Override
public void clearEsAllDate() throws IOException {
DeleteByQueryRequest deleteByQueryRequest = new DeleteByQueryRequest("es");
deleteByQueryRequest.setQuery(new MatchAllQueryBuilder());
deleteByQueryRequest.setConflicts("proceed");//版本冲突不中止进程
highLevelClient.deleteByQuery(deleteByQueryRequest, RequestOptions.DEFAULT);
}
分页获取文档
直接查看搜索文档
可以看到设置sourceBuilder的form和size属性就是分页查询,再对query设置查下全部即可。我们还可以设置超时时间。
设置好sourceBuilder后,我们就可以把它传给搜索请求了。接下来处理返回值即可。
代码:
/**
* 获得所有es文档
*/
@Override
public List<Poem> getAllEs() throws IOException {
List<Poem> result = new ArrayList<>();
SearchRequest searchRequest = new SearchRequest("es");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchAllQuery());
sourceBuilder.from(0);
sourceBuilder.size(10);
searchRequest.source(sourceBuilder);
SearchResponse search = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = search.getHits();
SearchHit[] hits = searchHits.getHits();
if ( hits != null && hits.length != 0 ){
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
Poem poem = JSON.parseObject(sourceAsString, Poem.class);
result.add(poem);
}
}
return result;
}
高亮搜索
查询官方文档中的Highlighting请求
我们首先设置SearchSourceBuilder和HighlightBuilder。
设置HighlightBuilder中的filed为高亮的字段,preTags和postTags则是我们的高亮字段的html。
利用boolQuery设置多个字段搜索。其中配置好优先级boost(越大优先级越高)以及分词器。
//设置搜索字段
sourceBuilder.query(QueryBuilders.boolQuery()
.should(QueryBuilders.multiMatchQuery(searchString,"title").boost(4).analyzer("ik_smart"))
.should(QueryBuilders.multiMatchQuery(searchString,"author").boost(3).analyzer("ik_smart"))
.should(QueryBuilders.multiMatchQuery(searchString,"content").boost(2).analyzer("ik_smart"))
.should(QueryBuilders.multiMatchQuery(searchString,"authorDetail").boost(1).analyzer("ik_smart"))
);
在获得的结果中我们可以单独获取到,高亮的字段。因为高亮的字段String和实体内属性是区分开的。这里需要思考如何把带上高亮标签的String放入实体属性内。那么因为属性是不断变化的,我们不能写死程序,这里想到用反射获取实体类的set方法。利用set方法注入携带高亮标签的属性。
//获得高亮字段 设置实体
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
//s为实体字段名
for (String s : highlightFields.keySet()) {
HighlightField highlightField = highlightFields.get(s);
Text[] fragments = highlightField.getFragments();
//获取高亮字段的属性
StringBuilder sb = new StringBuilder();
for (Text fragment : fragments) {
sb.append(fragment.toString());
}
// 因为不知道实际的是哪个属性 所以使用反射获取到set方法
try{
Class<? extends Poem> aClass = poem.getClass();
//把字段转换为驼峰格式 与set方法命名规范一致 首字母大写
String formatString = StringUtils.convertToCamelCase(s);
//通过方法名获取方法
Method declaredMethod = aClass.getDeclaredMethod("set" + formatString,String.class);
//执行方法,把高亮标签注入
declaredMethod.invoke(poem,sb.toString());
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
代码:
/**
* 高亮搜索
*
* @param searchString 搜索的字符串
* @return 结果
*/
@Override
public List<Poem> search(String searchString) throws IOException {
//todo 加入redis热词记录
//先搜索es
SearchRequest searchRequest = new SearchRequest("es");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//设置高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
highlightBuilder.field("author");
highlightBuilder.field("content");
highlightBuilder.field("authorDetail");
highlightBuilder.preTags("<em style=\"color:red\" >");
highlightBuilder.postTags("</em>");
sourceBuilder.highlighter(highlightBuilder);
//设置搜索字段
sourceBuilder.query(QueryBuilders.boolQuery()
.should(QueryBuilders.multiMatchQuery(searchString,"title").boost(4).analyzer("ik_smart"))
.should(QueryBuilders.multiMatchQuery(searchString,"author").boost(3).analyzer("ik_smart"))
.should(QueryBuilders.multiMatchQuery(searchString,"content").boost(2).analyzer("ik_smart"))
.should(QueryBuilders.multiMatchQuery(searchString,"authorDetail").boost(1).analyzer("ik_smart"))
);
searchRequest.source(sourceBuilder);
//获得结果
SearchResponse search = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = search.getHits();
SearchHit[] hits = searchHits.getHits();
List<Poem> result = new ArrayList<>();
if (hits != null && hits.length != 0){
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
//转为实体类
Poem poem = JSON.parseObject(sourceAsString, Poem.class);
//获得高亮字段 设置实体
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
for (String s : highlightFields.keySet()) {
HighlightField highlightField = highlightFields.get(s);
Text[] fragments = highlightField.getFragments();
StringBuilder sb = new StringBuilder();
for (Text fragment : fragments) {
sb.append(fragment.toString());
}
// 因为不知道实际的是哪个属性 所以使用反射获取到set方法
try{
Class<? extends Poem> aClass = poem.getClass();
//转换为驼峰格式 首字母大写
String formatString = StringUtils.convertToCamelCase(s);
Method declaredMethod = aClass.getDeclaredMethod("set" + formatString,String.class);
declaredMethod.invoke(poem,sb.toString());
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
result.add(poem);
}
}
//todo 如果没有击中则搜索数据库
//todo 搜索到了之后 加入es
return result;
}
总结
只是简单记录一下学习es中的综合操作,大部分也很简单。目前也还有很多目标功能没有实现,最近比较忙,暂时先放下来做其他事情了。有空再回来试试吧。
但是虽然上面说过了,这里还是要说
推荐直接看官方英文文档!
推荐直接看官方英文文档!!
推荐直接看官方英文文档!!!