1. 概述
Java REST Client 有两种风格:
- Java Low Level REST Client :用于Elasticsearch的官方低级客户端。它允许通过http与Elasticsearch集群通信。将请求编排和响应反编排留给用户自己处理。它兼容所有的Elasticsearch版本。(PS:学过WebService的话,对编排与反编排这个概念应该不陌生。可以理解为对请求参数的封装,以及对响应结果的解析)
- Java High Level REST Client :用于Elasticsearch的官方高级客户端。它是基于低级客户端的,它提供很多API,并负责请求的编排与响应的反编排。(PS:就好比是,一个是传自己拼接好的字符串,并且自己解析返回的结果;而另一个是传对象,返回的结果也已经封装好了,直接是对象,更加规范了参数的名称以及格式,更加面对对象一点)
(PS:所谓低级与高级,我觉得一个很形象的比喻是,面向过程编程与面向对象编程)
在 Elasticsearch 7.0 中不建议使用TransportClient,并且在8.0中会完全删除TransportClient。因此,官方更建议我们用Java High Level REST Client,它执行HTTP请求,而不是序列号的Java请求。既然如此,这里就直接用高级了。
2. Java High Level REST Client (高级REST客户端)
2.1. Maven仓库
<dependency> groupId>org.elasticsearch.client</artifactId>elasticsearch-rest-high-level-clientversion>6.5.4> >
2.2. 依赖
- org.elasticsearch.client:elasticsearch-rest-client
- org.elasticsearch:elasticsearch
2.3. 初始化
RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost("localhost",9200,"http"),
高级客户端内部会创建低级客户端用于基于提供的builder执行请求。低级客户端维护一个连接池,并启动一些线程,因此当你用完以后应该关闭高级客户端,并且在内部它将会关闭低级客户端,以释放这些资源。关闭客户端可以使用close()方法:
client.close();
2.4. 文档API
2.4.1. 添加文档
IndexRequest
IndexRequest request = new IndexRequest("posts","doc","1"); String jsonString = "{\"user\":\"kimchy\",\"postDate\":\"2013-01-30\",\"message\":\"trying out Elasticsearch\"}"; request.source(jsonString,XContentType.JSON);
提供文档source的方式还有很多,比如:
通过Map的方式提供文档source
通过XContentBuilder方式提供source
通过Object的方式(键值对)提供source
可选参数
同步执行
异步执行
你也可以异步执行 IndexRequest,为此你需要指定一个监听器来处理这个异步响应结果:
一个典型的监听器看起来是这样的:
IndexResponse
如果有版本冲突,将会抛出ElasticsearchException
同样的异常也有可能发生在当opType设置为create的时候,且相同索引、相同类型、相同ID的文档已经存在时。例如:
2.4.2. 查看文档
Get Request
可选参数
同步执行
异步执行
Get Response
当索引不存在,或者指定的文档的版本不存在时,响应状态吗是404,并且抛出ElasticsearchException
2.4.3. 文档是否存在
2.4.4. 删除文档
Delete Request
可选参数
同添加
2.5. 搜索API
Search Request
基本格式是这样的:
大多数查询参数被添加到 SearchSourceBuilder
可选参数
SearchSourceBuilder
控制检索行为的大部分选项都可以在SearchSourceBuilder中设置。下面是一个常见选项的例子:
在这个例子中,我们首先创建了一个SearchSourceBuilder对象,并且带着默认选项。然后设置了一个term查询,接着设置检索的位置和数量,最后设置超时时间
在设置完这些选项以后,我们只需要把SearchSourceBuilder加入到SearchRequest中即可
构建Query
用QueryBuilder来创建Serarch Query。QueryBuilder支持Elasticsearch DSL中每一种Query
例如:
还可以通过QueryBuilders工具类来创建QueryBuilder对象,例如:
无论是用哪种方式创建,最后一定要把QueryBuilder添加到SearchSourceBuilder中
排序
SearchSourceBuilder 可以添加一个或多个 SortBuilder
SortBuilder有四种实现:FieldSortBuilder、GeoDistanceSortBuilder、scoreSortBuilder、ScriptSortBuilder
聚集函数
同步执行
异步执行
从查询响应中取出文档
3. 示例
3.1. 准备数据
3.1.1. 安装IK分词器插件
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.5.4/elasticsearch-analysis-ik-6.5.4.zip
3.1.2. 创建索引
curl -X PUT "localhost:9200/book" -H 'Content-Type: application/json' -d' { mappings":{ _doc:{ properties:{ id:{ type":integer },nametext,1)">analyzerik_max_wordsearch_analyzerauthorcategorypricedoublestatusshortsellReasonsellTimedateformatyyyy-MM-dd } } } } } '
3.1.3. 数据预览
3.2. 示例代码
3.2.1. 完整的pom.xml
<?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.0parent> >org.springframework.boot>spring-boot-starter-parent>2.1.1.RELEASErelativePath/> <!-- lookup parent from repository --> >com.cjs.example>elasticsearch-demo>0.0.1-SNAPSHOTnamedescription></> propertiesjava.version>1.8dependencies> >spring-boot-starter-web>spring-boot-starter-thymeleaf> >org.apache.commons>commons-lang3>3.8>com.alibaba>fastjson>1.2.54>ch.qos.logback>logback-core>1.2.3>org.projectlombok>lombokoptional>true>spring-boot-starter-testscope>testbuildpluginsplugin> >spring-boot-maven-plugin> project>
3.2.2. 配置
package com.cjs.example.elasticsearch.config; import org.apache.http.HttpHost; org.elasticsearch.client.RestClient; org.elasticsearch.client.RestHighLevelClient; org.springframework.context.annotation.Bean; org.springframework.context.annotation.Configuration; /** * @author ChengJianSheng * @date 2019-01-07 */ @Configuration public class ElasticsearchClientConfig { @Bean public RestHighLevelClient restHighLevelClient() { RestHighLevelClient client = RestHighLevelClient( RestClient.builder( ))); return client; } }
3.2.3. domain
com.cjs.example.elasticsearch.domain.model; lombok.Data; java.io.Serializable; * 图书 * @Data class BookModel implements Serializable { private Integer id; 图书ID private String name; 图书名称 private String author; 作者 private Integer category; 图书分类 private Double price; 图书价格 private String sellReason; 上架理由 private String sellTime; 上架时间 private Integer status; 状态(1:可售,0:不可售) }
3.2.4. Controller
com.cjs.example.elasticsearch.controller; com.alibaba.fastjson.JSON; com.cjs.example.elasticsearch.domain.common.BaseResult; com.cjs.example.elasticsearch.domain.common.Page; com.cjs.example.elasticsearch.domain.model.BookModel; com.cjs.example.elasticsearch.domain.vo.BookRequestVO; com.cjs.example.elasticsearch.service.BookService; lombok.extern.slf4j.Slf4j; org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; * 文档操作 * @Slf4j @RestController @RequestMapping("/book") BookController { @Autowired private BookService bookService; * 列表分页查询 @GetMapping("/list") BaseResult list(BookRequestVO bookRequestVO) { Page<BookModel> page = bookService.list(bookRequestVO); if (null == page) { BaseResult.error(); } BaseResult.ok(page); } * 查看文档 @GetMapping("/detail" BaseResult detail(Integer id) { id) { return BaseResult.error("ID不能为空"); } BookModel book = bookService.detail(id); BaseResult.ok(book); } * 添加文档 @PostMapping("/add" BaseResult add(@RequestBody BookModel bookModel) { bookService.save(bookModel); log.info("插入文档成功!请求参数: {}" BaseResult.ok(); } * 修改文档 @PostMapping("/update" BaseResult update(@RequestBody BookModel bookModel) { Integer id = bookModel.getId(); book) { return BaseResult.error("记录不存在"); } bookService.update(bookModel); log.info("更新文档成功!请求参数: {}" * 删除文档 @GetMapping("/delete" BaseResult delete(Integer id) { ); } bookService.delete(id); BaseResult.ok(); } }
3.2.5. Service
com.cjs.example.elasticsearch.service.impl; org.apache.commons.lang3.StringUtils; org.elasticsearch.action.ActionListener; org.elasticsearch.action.DocWriteResponse; org.elasticsearch.action.delete.DeleteRequest; org.elasticsearch.action.delete.DeleteResponse; org.elasticsearch.action.get.GetRequest; org.elasticsearch.action.get.GetResponse; org.elasticsearch.action.index.IndexRequest; org.elasticsearch.action.index.IndexResponse; org.elasticsearch.action.search.SearchRequest; org.elasticsearch.action.search.SearchResponse; org.elasticsearch.action.support.replication.ReplicationResponse; org.elasticsearch.action.update.UpdateRequest; org.elasticsearch.action.update.UpdateResponse; org.elasticsearch.client.RequestOptions; org.elasticsearch.common.unit.TimeValue; org.elasticsearch.index.query.BoolQueryBuilder; org.elasticsearch.index.query.QueryBuilders; org.elasticsearch.rest.RestStatus; org.elasticsearch.search.SearchHit; org.elasticsearch.search.SearchHits; org.elasticsearch.search.builder.SearchSourceBuilder; org.elasticsearch.search.sort.FieldSortBuilder; org.elasticsearch.search.sort.SortOrder; org.springframework.stereotype.Service; org.springframework.util.CollectionUtils; java.io.IOException; import java.util.*; java.util.stream.Collectors; @Slf4j @Service class BookServiceImpl BookService { private static final String INDEX_NAME = "book"; final String INDEX_TYPE = "_doc"; @Autowired RestHighLevelClient client; @Override public Page<BookModel> list(BookRequestVO bookRequestVO) { int pageNo = bookRequestVO.getPageNo(); int pageSize = bookRequestVO.getPageSize(); SearchSourceBuilder sourceBuilder = SearchSourceBuilder(); sourceBuilder.from(pageNo - 1); sourceBuilder.size(pageSize); sourceBuilder.sort(new FieldSortBuilder("id").order(SortOrder.ASC)); sourceBuilder.query(QueryBuilders.matchAllQuery()); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); if (StringUtils.isNotBlank(bookRequestVO.getName())) { boolQueryBuilder.must(QueryBuilders.matchQuery("name" (StringUtils.isNotBlank(bookRequestVO.getAuthor())) { boolQueryBuilder.must(QueryBuilders.matchQuery("author"null != bookRequestVO.getStatus()) { boolQueryBuilder.must(QueryBuilders.termQuery("status" (StringUtils.isNotBlank(bookRequestVO.getSellTime())) { boolQueryBuilder.must(QueryBuilders.termQuery("sellTime" (StringUtils.isNotBlank(bookRequestVO.getCategories())) { String[] categoryArr = bookRequestVO.getCategories().split(","); List<Integer> categoryList = Arrays.asList(categoryArr).stream().map(e->Integer.valueOf(e)).collect(Collectors.toList()); BoolQueryBuilder categoryBoolQueryBuilder = QueryBuilders.boolQuery(); for (Integer category : categoryList) { categoryBoolQueryBuilder.should(QueryBuilders.termQuery("category" SearchRequest(); searchRequest.indices(INDEX_NAME); searchRequest.source(sourceBuilder); try { SearchResponse searchResponse = client.search(searchRequest,RequestOptions.DEFAULT); RestStatus restStatus = searchResponse.status(); if (restStatus != RestStatus.OK) { return null; } List<BookModel> list = new ArrayList<>(); SearchHits searchHits = searchResponse.getHits(); (SearchHit hit : searchHits.getHits()) { String source = hit.getSourceAsString(); BookModel book = JSON.parSEObject(source,BookModel.); list.add(book); } long totalHits = searchHits.getTotalHits(); Page<BookModel> page = new Page<>(pageNo,pageSize,totalHits,list); TimeValue took = searchResponse.getTook(); log.info("查询成功!请求参数: {},用时{}毫秒" page; } catch (IOException e) { log.error("查询失败!原因: {}"; } @Override void save(BookModel bookModel) { Map<String,Object> jsonMap = new HashMap<>(); jsonMap.put("id" IndexRequest(INDEX_NAME,INDEX_TYPE,String.valueOf(bookModel.getId())); indexRequest.source(jsonMap); client.indexAsync(indexRequest,RequestOptions.DEFAULT,1)">new ActionListener<IndexResponse>() { @Override onResponse(IndexResponse indexResponse) { String index = indexResponse.getIndex(); String type = indexResponse.getType(); String id = indexResponse.getId(); long version = indexResponse.getVersion(); log.info("Index: {},Type: {},Id: {},Version: {}"if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) { log.info("写入文档"); } else DocWriteResponse.Result.UPDATED) { log.info("修改文档"); } ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo(); if (shardInfo.getTotal() != shardInfo.getSuccessful()) { log.warn("部分分片写入成功"); } if (shardInfo.getFailed() > 0) { (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) { String reason = failure.reason(); log.warn("失败原因: {}" onFailure(Exception e) { log.error(e.getMessage(),e); } }); } @Override update(BookModel bookModel) { Map<String,1)">(); jsonMap.put("sellReason" UpdateRequest(INDEX_NAME,String.valueOf(bookModel.getId())); request.doc(jsonMap); { UpdateResponse updateResponse = client.update(request,RequestOptions.DEFAULT); } (IOException e) { log.error("更新失败!原因: {}"void delete(int id) { DeleteRequest request = DeleteRequest(INDEX_NAME,String.valueOf(id)); { DeleteResponse deleteResponse = client.delete(request,RequestOptions.DEFAULT); if (deleteResponse.status() == RestStatus.OK) { log.info("删除成功!id: {}" (IOException e) { log.error("删除失败!原因: {}"public BookModel detail( id) { GetRequest getRequest = GetRequest(INDEX_NAME,1)"> { GetResponse getResponse = client.get(getRequest,1)"> (getResponse.isExists()) { String source = getResponse.getSourceAsString(); BookModel book = JSON.parSEObject(source,1)">); book; } } (IOException e) { log.error("查看失败!原因: {}"; } }
3.2.6. 页面
<!DOCTYPE htmlhtml lang="zh"headMeta charset="UTF-8"title>图书列表link rel="stylesheet" href="/bootstrap-4/css/bootstrap.min.css"="/bootstrap-table/bootstrap-table.css"script src="jquery-3.3.1.min.js"script="/bootstrap-4/js/bootstrap.min.js"="/bootstrap-table/bootstrap-table.js"="/bootstrap-table/locale/bootstrap-table-zh-CN.js"> $(function(){ $('#table).bootstrapTable({ url: /book/listgetserver(res) { // 加载服务器数据之前的处理程序,可以用来格式化数据。参数:res为从服务器请求到的数据。 var result = {}; result.total res.data.totalCount; result.rows res.data.pageList; return result; },pagination: true3 初始PageSize queryParams: (params) { req { pageSize: params.limit,pageNo: params.offset + 1 }; req; },striped: idIDname名称author作者price单价sellTime上架时间status状态(value) { if (value == ) { return <span style="color: green">可售</span>; } else { <span style="color: red">不可售</span>; } } },1)">category分类10010中国当代小说else 10011武侠小说10012爱情小说10013中国当代随笔sellReason上架理由操作() { <a href="#">修改</a> <a href="#">删除</a>; } } ] }); }); bodydiv ="table-responsive" style="padding: 10px 30px"table id="table" class="table text-nowrap"tabledivhtml>
3.3. 演示
重点演示几个查询
返回结果:
{ "code": 200,"success": true,"msg": "SUCCESS","data": { "pageNumber": 1,"pageSize": 10,"totalCount": 2,"pageList": [ { "id": 2,"name": "倚天屠龙记(全四册)","author": "金庸","category": 10011,"price": 70.4,"sellReason": "武林至尊,宝刀屠龙,号令天下,莫敢不从。","sellTime": "2018-11-11","status": 1 },{ "id": 3,"name": "神雕侠侣","price": 70,"sellReason": "风陵渡口初相遇,一见杨过误终身","status": 1 } ] } }
上面的查询对应的Elasticsearch DSL是这样的:
{ "from":0,"size":10,"query":{ "bool":{ "must":[ { "match":{ "author":{ "query":"金庸","operator":"OR","prefix_length":0,"max_expansions":50,"fuzzy_transpositions":true,"lenient":false,"zero_terms_query":"NONE","auto_generate_synonyms_phrase_query":true,"boost":1 } } },{ "term":{ "status":{ "value":1,{ "bool":{ "should":[ { "term":{ "category":{ "value":10010,"boost":1 } } },{ "term":{ "category":{ "value":10011,{ "term":{ "category":{ "value":10012,"boost":1 } } } ],"adjust_pure_negative":true,"boost":1 } } ],"boost":1 } },"sort":[ { "id":{ "order":"asc" } } ] }
3.4. 工程结构
4. 参考
https://github.com/medcl/elasticsearch-analysis-ik
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html
https://bootstrap-table.wenzhixin.net.cn/documentation/
5. 其它相关
原文链接:/elasticsearch/882730.html