1. 概述css
Java REST Client 有兩種風格:html
(PS:所謂低級與高級,我以爲一個很形象的比喻是,面向過程編程與面向對象編程)java
在 Elasticsearch 7.0 中不建議使用TransportClient,而且在8.0中會徹底刪除TransportClient。所以,官方更建議咱們用Java High Level REST Client,它執行HTTP請求,而不是序列號的Java請求。既然如此,這裏就直接用高級了。jquery
2. Java High Level REST Client (高級REST客戶端)git
2.1. Maven倉庫github
<dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>6.5.4</version> </dependency>
2.2. 依賴web
2.3. 初始化spring
RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost("localhost", 9200, "http"), new HttpHost("localhost", 9201, "http")));
高級客戶端內部會建立低級客戶端用於基於提供的builder執行請求。低級客戶端維護一個鏈接池,並啓動一些線程,所以當你用完之後應該關閉高級客戶端,而且在內部它將會關閉低級客戶端,以釋放這些資源。關閉客戶端可使用close()方法:apache
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" }, "name":{ "type":"text", "analyzer":"ik_max_word", "search_analyzer":"ik_max_word" }, "author":{ "type":"text", "analyzer":"ik_max_word", "search_analyzer":"ik_max_word" }, "category":{ "type":"integer" }, "price":{ "type":"double" }, "status":{ "type":"short" }, "sellReason":{ "type":"text", "analyzer":"ik_max_word", "search_analyzer":"ik_max_word" }, "sellTime":{ "type":"date", "format":"yyyy-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.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.cjs.example</groupId> <artifactId>elasticsearch-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>elasticsearch-demo</name> <description></description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>6.5.4</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.8</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.54</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3.2.2. 配置
package com.cjs.example.elasticsearch.config; import org.apache.http.HttpHost; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestHighLevelClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author ChengJianSheng * @date 2019-01-07 */ @Configuration public class ElasticsearchClientConfig { @Bean public RestHighLevelClient restHighLevelClient() { RestHighLevelClient client = new RestHighLevelClient( RestClient.builder( new HttpHost("localhost", 9200, "http"))); return client; } }
3.2.3. domain
package com.cjs.example.elasticsearch.domain.model; import lombok.Data; import java.io.Serializable; /** * 圖書 * @author ChengJianSheng * @date 2019-01-07 */ @Data public 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
package com.cjs.example.elasticsearch.controller; import com.alibaba.fastjson.JSON; import com.cjs.example.elasticsearch.domain.common.BaseResult; import com.cjs.example.elasticsearch.domain.common.Page; import com.cjs.example.elasticsearch.domain.model.BookModel; import com.cjs.example.elasticsearch.domain.vo.BookRequestVO; import com.cjs.example.elasticsearch.service.BookService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * 文檔操做 * @author ChengJianSheng * @date 2019-01-07 */ @Slf4j @RestController @RequestMapping("/book") public class BookController { @Autowired private BookService bookService; /** * 列表分頁查詢 */ @GetMapping("/list") public BaseResult list(BookRequestVO bookRequestVO) { Page<BookModel> page = bookService.list(bookRequestVO); if (null == page) { return BaseResult.error(); } return BaseResult.ok(page); } /** * 查看文檔 */ @GetMapping("/detail") public BaseResult detail(Integer id) { if (null == id) { return BaseResult.error("ID不能爲空"); } BookModel book = bookService.detail(id); return BaseResult.ok(book); } /** * 添加文檔 */ @PostMapping("/add") public BaseResult add(@RequestBody BookModel bookModel) { bookService.save(bookModel); log.info("插入文檔成功!請求參數: {}", JSON.toJSONString(bookModel)); return BaseResult.ok(); } /** * 修改文檔 */ @PostMapping("/update") public BaseResult update(@RequestBody BookModel bookModel) { Integer id = bookModel.getId(); if (null == id) { return BaseResult.error("ID不能爲空"); } BookModel book = bookService.detail(id); if (null == book) { return BaseResult.error("記錄不存在"); } bookService.update(bookModel); log.info("更新文檔成功!請求參數: {}", JSON.toJSONString(bookModel)); return BaseResult.ok(); } /** * 刪除文檔 */ @GetMapping("/delete") public BaseResult delete(Integer id) { if (null == id) { return BaseResult.error("ID不能爲空"); } bookService.delete(id); return BaseResult.ok(); } }
3.2.5. Service
package com.cjs.example.elasticsearch.service.impl; import com.alibaba.fastjson.JSON; import com.cjs.example.elasticsearch.domain.common.Page; import com.cjs.example.elasticsearch.domain.model.BookModel; import com.cjs.example.elasticsearch.domain.vo.BookRequestVO; import com.cjs.example.elasticsearch.service.BookService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.DocWriteResponse; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.replication.ReplicationResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.io.IOException; import java.util.*; import java.util.stream.Collectors; /** * @author ChengJianSheng * @date 2019-01-07 */ @Slf4j @Service public class BookServiceImpl implements BookService { private static final String INDEX_NAME = "book"; private static final String INDEX_TYPE = "_doc"; @Autowired private RestHighLevelClient client; @Override public Page<BookModel> list(BookRequestVO bookRequestVO) { int pageNo = bookRequestVO.getPageNo(); int pageSize = bookRequestVO.getPageSize(); SearchSourceBuilder sourceBuilder = new 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", bookRequestVO.getName())); } if (StringUtils.isNotBlank(bookRequestVO.getAuthor())) { boolQueryBuilder.must(QueryBuilders.matchQuery("author", bookRequestVO.getAuthor())); } if (null != bookRequestVO.getStatus()) { boolQueryBuilder.must(QueryBuilders.termQuery("status", bookRequestVO.getStatus())); } if (StringUtils.isNotBlank(bookRequestVO.getSellTime())) { boolQueryBuilder.must(QueryBuilders.termQuery("sellTime", bookRequestVO.getSellTime())); } if (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", category)); } boolQueryBuilder.must(categoryBoolQueryBuilder); } sourceBuilder.query(boolQueryBuilder); SearchRequest searchRequest = new 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(); for (SearchHit hit : searchHits.getHits()) { String source = hit.getSourceAsString(); BookModel book = JSON.parseObject(source, BookModel.class); list.add(book); } long totalHits = searchHits.getTotalHits(); Page<BookModel> page = new Page<>(pageNo, pageSize, totalHits, list); TimeValue took = searchResponse.getTook(); log.info("查詢成功!請求參數: {}, 用時{}毫秒", searchRequest.source().toString(), took.millis()); return page; } catch (IOException e) { log.error("查詢失敗!緣由: {}", e.getMessage(), e); } return null; } @Override public void save(BookModel bookModel) { Map<String, Object> jsonMap = new HashMap<>(); jsonMap.put("id", bookModel.getId()); jsonMap.put("name", bookModel.getName()); jsonMap.put("author", bookModel.getAuthor()); jsonMap.put("category", bookModel.getCategory()); jsonMap.put("price", bookModel.getPrice()); jsonMap.put("sellTime", bookModel.getSellTime()); jsonMap.put("sellReason", bookModel.getSellReason()); jsonMap.put("status", bookModel.getStatus()); IndexRequest indexRequest = new IndexRequest(INDEX_NAME, INDEX_TYPE, String.valueOf(bookModel.getId())); indexRequest.source(jsonMap); client.indexAsync(indexRequest, RequestOptions.DEFAULT, new ActionListener<IndexResponse>() { @Override public void 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: {}", index, type, id, version); if (indexResponse.getResult() == DocWriteResponse.Result.CREATED) { log.info("寫入文檔"); } else if (indexResponse.getResult() == DocWriteResponse.Result.UPDATED) { log.info("修改文檔"); } ReplicationResponse.ShardInfo shardInfo = indexResponse.getShardInfo(); if (shardInfo.getTotal() != shardInfo.getSuccessful()) { log.warn("部分分片寫入成功"); } if (shardInfo.getFailed() > 0) { for (ReplicationResponse.ShardInfo.Failure failure : shardInfo.getFailures()) { String reason = failure.reason(); log.warn("失敗緣由: {}", reason); } } } @Override public void onFailure(Exception e) { log.error(e.getMessage(), e); } }); } @Override public void update(BookModel bookModel) { Map<String, Object> jsonMap = new HashMap<>(); jsonMap.put("sellReason", bookModel.getSellReason()); UpdateRequest request = new UpdateRequest(INDEX_NAME, INDEX_TYPE, String.valueOf(bookModel.getId())); request.doc(jsonMap); try { UpdateResponse updateResponse = client.update(request, RequestOptions.DEFAULT); } catch (IOException e) { log.error("更新失敗!緣由: {}", e.getMessage(), e); } } @Override public void delete(int id) { DeleteRequest request = new DeleteRequest(INDEX_NAME, INDEX_TYPE, String.valueOf(id)); try { DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT); if (deleteResponse.status() == RestStatus.OK) { log.info("刪除成功!id: {}", id); } } catch (IOException e) { log.error("刪除失敗!緣由: {}", e.getMessage(), e); } } @Override public BookModel detail(int id) { GetRequest getRequest = new GetRequest(INDEX_NAME, INDEX_TYPE, String.valueOf(id)); try { GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT); if (getResponse.isExists()) { String source = getResponse.getSourceAsString(); BookModel book = JSON.parseObject(source, BookModel.class); return book; } } catch (IOException e) { log.error("查看失敗!緣由: {}", e.getMessage(), e); } return null; } }
3.2.6. 頁面
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>圖書列表</title> <link rel="stylesheet" href="/bootstrap-4/css/bootstrap.min.css"> <link rel="stylesheet" href="/bootstrap-table/bootstrap-table.css"> <script src="jquery-3.3.1.min.js"></script> <script src="/bootstrap-4/js/bootstrap.min.js"></script> <script src="/bootstrap-table/bootstrap-table.js"></script> <script src="/bootstrap-table/locale/bootstrap-table-zh-CN.js"></script> <script> $(function(){ $('#table').bootstrapTable({ url: '/book/list', method: 'get', sidePagination: 'server', responseHandler: function(res) { // 加載服務器數據以前的處理程序,能夠用來格式化數據。參數:res爲從服務器請求到的數據。 var result = {}; result.total = res.data.totalCount; result.rows = res.data.pageList; return result; }, pagination: true, pageSize: 3, // 初始PageSize queryParams: function(params) { var req = { pageSize: params.limit, pageNo: params.offset + 1 }; return req; }, striped: true, search: true, columns: [{ field: 'id', title: 'ID' }, { field: 'name', title: '名稱' }, { field: 'author', title: '做者' }, { field: 'price', title: '單價' }, { field: 'sellTime', title: '上架時間' }, { field: 'status', title: '狀態', formatter: function(value) { if (value == 1) { return '<span style="color: green">可售</span>'; } else { return '<span style="color: red">不可售</span>'; } } }, { field: 'category', title: '分類', formatter: function(value) { if (value == 10010) { return '中國當代小說'; } else if (value == 10011) { return '武俠小說'; } else if (value == 10012) { return '愛情小說'; } else if (value == 10013) { return '中國當代隨筆'; } } }, { field: 'sellReason', title: '上架理由' }, { title: '操做', formatter: function() { return '<a href="#">修改</a> <a href="#">刪除</a>'; } } ] }); }); </script> </head> <body> <div class="table-responsive" style="padding: 10px 30px"> <table id="table" class="table text-nowrap"></table> </div> </body> </html>
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": "神鵰俠侶", "author": "金庸", "category": 10011, "price": 70, "sellReason": "風陵渡口初相遇,一見楊過誤終身", "sellTime": "2018-11-11", "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, "boost":1 } } }, { "bool":{ "should":[ { "term":{ "category":{ "value":10010, "boost":1 } } }, { "term":{ "category":{ "value":10011, "boost":1 } } }, { "term":{ "category":{ "value":10012, "boost":1 } } } ], "adjust_pure_negative":true, "boost":1 } } ], "adjust_pure_negative":true, "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. 其它相關