我使用ElasticSearch-7.5.2基于springboot开发。
7.x算是比较新的版本,网上关于es的api更多是transport API的说明。这里使用的是Java REST Client,所以在这里记录一下,仅供参考。
Java REST客户端有两种版本:
Java Low Level REST Client
:Elasticsearch的官方低级客户端。它允许通过http与Elasticsearch集群进行通信。将请求编组和对用户取消编组的响应离开。它与所有Elasticsearch版本兼容。
Java High Level REST Client
:Elasticsearch的官方高级客户端。基于低级客户端,它公开了API特定的方法,并负责请求编组和响应编组。
关于高级REST客户端的使用都在官方文档,包括需要的maven依赖,初始化,Index APIs,Document APIs,Search APIs等等,在使用过程中我被一个问题困扰过,就是在创建索引时的参数settings、mapping等设置,其实都在Index APIs文档里都有。
官方Java REST Client文档连接:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/index.html
具体的探索过程体现在问题集锦中了,包括一些源码。
整个探索ES过程中的问题集锦:
1、操作es时用elsearch用户操作!!不然容易产生其他用户的文件,导致再启动时权限不足问题。
2、安装ik中文分词器时,重启报错:
Caused by: java.nio.file.NoSuchFileException: /data/es/elasticsearch-7.5.2/plugins/config/plugin-descriptor.properties
解决:因为我是直接从github上直接下载的编译好的zip压缩包,我直接解压到elasticsearch的根目录下的./plugins目录下,但是启动就是出错,网上搜发现有说要建一个ik目录在./plugins下面,然后把解压的ik插件放进去就可以了。
配置ik中文分词器,并测验两种分此效果(ik_max_word、ik_smart)。
3、创建索引时指定了索引type:
"type": "illegal_argument_exception",
"reason": "The mapping definition cannot be nested under a type [_doc] unless include_type_name is set to true."
首先明确两个概念index的type ,以及mapping中field的type。
7.0的中index的type已经默认禁止使用了。
解决:
1)、PUT index/_mappings?include_type_name=true
当成一个parameter去使用
2)、另外7.0的时候,不写type就不会涉及到这方面的问题。网上很多文章都是基于旧版本的,在url中写了type,使用的过程中注意一些应该就好了。
4、在探索es搜索txt文本过程记录一个问题:
PUT /xingfa
{"settings": {
"index" : {
"number_of_shards":3,
"number_of_replicas":2
}
},
"mappings": {
"properties": {
"text_entry": {
"type": "keyword",
"fields":{
"keyword":{
"type":"keyword",
"ignore_above":256
}
}
}
}
}
}
这是加了mappings建的索引,结果!!!导入的数据就是不能模糊搜索,就是在使用match搜索时必须得整个内容作为关键词才能搜到。
所以把mappings去掉就行了!!
PUT /xingfa
{"settings": {
"index" : {
"number_of_shards":3,
"number_of_replicas":2
}
}
}
愚蠢的我!!后来发现是因为字段类型是keyword!!
首先,明确字段是否需要分词,不需要分词的字段将type设置为keyword,可以节省空间和提高写性能。
5、springboot 集成es API过程问题集锦
5.1、创建springboot项目时,在选择dependencies时默认的Developer Tools都勾选了!
5.2、vue的依赖问题:网页控制台报错 Vue is not defined 需要重启idea。不知道为什么。
5.3、Springboot文件上传出现找不到指定系统路径
上传的路径之前要存在,不然报这个错,可以加一个判断。
5.4、查询过程:精确匹配并支持中文问题:
最开始的现象是中文只能通过全部名称keyword的形式匹配到。
两种方法:
1 一个中文进行搜索可以。但是并不够智能,匹配到一堆。
2 创建索引时添加index":"true
{
"properties":{
"name":{
"type":"text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
}
}
}
request.mapping(
"{\n" +
" \"properties\":{\n" +
" \"name\":{\n" +
" \"type\":\"keyword\",\n" +
" \"index\":\"true\"\n" +
" }\n" +
" }\n" +
"}",
XContentType.JSON);
!!但是 还是有瑕疵,这种情况只能通过全部中文内容来匹配,比如:‘民法典’来匹配 name:民法典
后来发现是因为字段类型是keyword!!
明确字段是否需要分词,不需要分词的字段将type设置为keyword,可以节省空间和提高写性能。
所以最后的解决还是:
今日内容:1) es IndexAPI在创建索引时映射指定中文分词器ik,匹配类型ik_smart。
2) search API使用boolQuery实现精准匹配。
1) XContentBuilder jsonBuilder = XContentFactory.jsonBuilder();
jsonBuilder.startObject();
{
jsonBuilder.startObject("properties");
{
jsonBuilder.startObject("name");
{
// 不需要分词的字段将type设置为keyword,可以节省空间和提高写性能。
jsonBuilder.field("type","keyword");
}
jsonBuilder.endObject();
jsonBuilder.startObject("content");
{
jsonBuilder.field("type","text");
jsonBuilder.field("analyzer","ik_smart");
jsonBuilder.field("search_analyzer","ik_smart");
}
jsonBuilder.endObject();
}
jsonBuilder.endObject();
}
jsonBuilder.endObject();
// 将mapping添加到请求中
request.mapping(jsonBuilder);
2) // 复杂搜索逻辑的BoolQuery用法
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery
.must(QueryBuilders.termQuery("name",bookName))
.must(QueryBuilders.matchQuery("content",keyword));
sourceBuilder.query(boolQuery);
5.5 两个字段(标题和内容)进行搜索,均能高亮显示。
这个探索过程比较详细,可以推翻5.4的一些说法,以更标准的方法实现一些功能。
比如我用的中文分词器ik_max_word,也只有搜索的关键词在两个及两个字以上时才存在分词,这样的话搜索的关键词只有一个字时,就怎么搜都搜不到。网上了解后,elasticsearch默认的分词原理就是将你的关键词分成一个个字(可以参考https://blog.youkuaiyun.com/boling_cavalry/article/details/86549043),但是并不能很好的支持单个字的搜索。
在网上不停的查看资料,逐渐察觉到搜索这件事=分词+搜索方法一起完成的。
但是也有的一些搜索方法不分词:wildcard 通配符搜索、regexp 正则搜索、prefix 前缀搜索等(参考https://blog.youkuaiyun.com/cs1509235061/article/details/89454145)。
所以这里我用的是通配符搜索QueryBuilders.wildcardQuery实现单个字搜索,这样,当搜索关键词是一个字时解决了。当搜索关键词多个词时wildcardQuery就显得并不智能了,所以使用 match搜索+ik_max_word分词。说了这么多献上部分源码:
QueryBuilder queryBuilder = null;
// 判断搜索关键字是否是一个汉字,如果是使用通配符搜索 wildcardQuery
if(keyword.length() == 1){
// 通配符搜索 wildcardQuery就不支持分词了
WildcardQueryBuilder name = QueryBuilders.wildcardQuery("name", "*" + keyword + "*");
WildcardQueryBuilder text = QueryBuilders.wildcardQuery("text", "*" + keyword + "*");
queryBuilder = QueryBuilders
.boolQuery()
.should(name)
.should(text);
}else{
MatchQueryBuilder queryBuilder_name = QueryBuilders.matchQuery("name", keyword);
MatchQueryBuilder queryBuilder_text = QueryBuilders.matchQuery("text", keyword);
queryBuilder = QueryBuilders
.boolQuery()
.should(queryBuilder_name)
.should(queryBuilder_text);
}
boolQuery的使用是因为有两个字段进行搜索匹配。
提一下,两个字段都支持分词的话,前面在创建该索引时mapping里记得给这俩字段都设置上search_analyzer = ik_max_word:
XContentBuilder jsonBuilder = XContentFactory.jsonBuilder();
jsonBuilder.startObject();
{
jsonBuilder.startObject("properties");
{
jsonBuilder.startObject("name");
{
jsonBuilder.field("type", "text");
jsonBuilder.field("analyzer", "ik_max_word");
jsonBuilder.field("search_analyzer", "ik_max_word"); // ik_smart
}
jsonBuilder.endObject();
jsonBuilder.startObject("text");
{
jsonBuilder.field("type", "text");
jsonBuilder.field("analyzer", "ik_max_word");
jsonBuilder.field("search_analyzer", "ik_max_word"); // ik_smart
}
jsonBuilder.endObject();
}
jsonBuilder.endObject();
}
jsonBuilder.endObject();
request.mapping(jsonBuilder);
接下来讲讲高亮问题:
最开始我以为在HighlightBuilder里设置了高亮字段就可以了,但是完全没那么简单,这样做仅仅是在response里有了HighlightField这个东西,仅此而已!不会有任何高亮!我们需要把高亮的内容解析到_source里。
// 3、设置高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
/*highlightBuilder.field("name");
highlightBuilder.field("text");
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");*/
HighlightBuilder.Field highlightName = new HighlightBuilder.Field("name");
HighlightBuilder.Field highlightText = new HighlightBuilder.Field("text");
highlightBuilder.field(highlightName);
highlightBuilder.field(highlightText);
highlightBuilder.highlighterType("unified");
sourceBuilder.highlighter(highlightBuilder);
// 5、解析高亮的字段
ArrayList<Map<String, Object>> list = new ArrayList<>();
for (SearchHit documentFields : searchResponse.getHits().getHits()) {
// 解析高亮的字段 前面source部分设置了高亮,这里才会有高亮字段与高亮内容
Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
HighlightField highlightFieldName = highlightFields.get("name");
HighlightField highlightFieldText = highlightFields.get("text");
// TODO 这是原来的结果,需要将高亮的内容更换进去
Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
if (highlightFieldName != null) {
Text[] fragments_name = highlightFieldName.fragments(); // 分段 再 重组
String n_name = "";
for (Text fragment : fragments_name) {
n_name += fragment;
}
sourceAsMap.put("name", n_name);
}
if (highlightFieldText != null) {
Text[] fragments_text = highlightFieldText.fragments(); // 分段 再 重组
String n_text = "";
for (Text fragment : fragments_text) {
n_text += fragment;
}
sourceAsMap.put("text", n_text);
}
list.add(sourceAsMap);
}
其实es还有好多功能我都还没研究透,但是今天用到这我就记录到这!