elastic search實戰小demo:https://github.com/simonsfan/springboot-quartz-demo,分支:feature_es
之前在 Elastic Search之Search API(Query DSL)、Elasticsearch之索引和文檔API 文章中講到過elastic search(以下簡稱es)的以下常用的原生api,本篇開始講述如何結合java開發使用es api進行索引的CRUD及複雜查詢。我這裏以springboot中使用爲例方便測試,首先需要引入maven依賴:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>5.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
springboot配置文件application.yml中加如下配置,注意9300是es與java交互的端口,不是es與http交互的端口號9200:
elasticsearch:
cluster:
name: elasticsearch
host: 127.0.0.1
port: 9300
建立測試用到的movie_index索引並且新增兩條測試數據:
PUT /movie_index
{
"mappings": {
"info": {
"properties": {
"actors": {
"type": "text"
},
"alias": {
"type": "text"
},
"directors": {
"type": "text"
},
"introduction": {
"type": "text"
},
"label": {
"type": "text"
},
"name": {
"type": "text"
},
"score": {
"type": "float"
},
"release": {
"type": "date"
},
"area": {
"type": "keyword"
}
}
}
}
}
POST /movie_index/info
{
"name": "毒液",
"alias": "毒液",
"actors": "湯姆·哈迪 米歇爾·威廉姆斯 伍迪·哈里森 里茲·阿邁德 珍妮·斯蕾特",
"directors": "魯本·弗雷斯徹",
"score": 9,
"area": "美國",
"label": "2018年 美國 電影 動作 好萊塢 VIP電影 VIP尊享 動作 新片",
"release": "2019",
"introduction": "017年3月17日,索尼宣佈將爲蜘蛛俠的死對頭“毒液”(Venom)打造外傳電影,並計劃於2018年10月5日上映。《毒液》被視作蜘蛛俠系列的外傳,將由《超凡蜘蛛俠2》的編劇艾裏克斯·庫茲曼(《木乃伊》)執導,《蜘蛛俠:英雄歸來》的製片人馬修·托馬齊以及漫威影業前CEO阿維·阿拉德擔任製片,由丹特·哈珀(《明日邊緣》)編劇。他們表示,此片與湯姆·赫蘭德主演的蜘蛛俠三部曲沒什麼關係,是一個獨立的外傳。關於此片的更多細節並未透露。 2017年3月28日,索尼確認《毒液》將以R級的形式進行開發。 2017年5月,確認英國演員湯姆·哈迪將出演漫威蜘蛛俠衍生片《毒液》,將扮演自由攝影師Eddie Brock 。 2017年6月,製片人艾米·帕斯卡爾證實影片將和漫威電影宇宙連接,作爲附屬電影,並且有機會讓湯姆·赫蘭德迴歸飾演蜘蛛俠。"
}
POST /movie_index/info
{
"name": "我不是藥神",
"alias": "我不是藥神",
"actors": "徐崢 王傳君 週一圍 譚卓 章宇",
"directors": "文牧野",
"score": 9.2,
"area": "內地",
"label": "國內院線 VIP電影 劇情 VIP尊享 院線 喜劇 喜劇 新片 劇情",
"release": "2018",
"introduction": "普通中年男子程勇(徐崢 飾)經營着一家保健品店,失意又失婚。不速之客呂受益(王傳君 飾)的到來,讓他開闢了一條去印度買藥做“代購”的新事業,雖然困難重重,但他在這條“買藥之路”上發現了商機,一發不可收拾地做起了治療慢粒白血病的印度仿製藥獨家代理商。賺錢的同時,他也認識了幾個病患及家屬,爲救女兒被迫做舞女的思慧(譚卓 飾)、說一口流利“神父腔”英語的劉牧師(楊新鳴 飾),以及脾氣暴烈的“黃毛”(章宇 飾),幾個人合夥做起了生意,利潤倍增的同時也危機四伏。程勇昔日的小舅子曹警官(週一圍 飾)奉命調查仿製藥的源頭,假藥販子張長林(王硯輝 飾)和瑞士正牌醫藥代表(李乃文 飾)也對其虎視眈眈,生意逐漸變成了一場關於救贖的拉鋸戰。"
}
首先注入elastic search的客戶端TransportClient:
@Configuration
public class ElasticSearchConfig {
@Value("${elasticsearch.host}")
private String esHost;
@Value("${elasticsearch.port}")
private int esPort;
@Value("${elasticsearch.cluster.name}")
private String esName;
@Bean
public TransportClient esClient() throws UnknownHostException {
TransportClient client = null;
try {
Settings settings = Settings.builder()
.put("client.transport.sniff", true)
.put("cluster.name", this.esName)
.build();
InetSocketTransportAddress master = new InetSocketTransportAddress(InetAddress.getByName(esHost), esPort);
client = new PreBuiltTransportClient(settings).addTransportAddress(master);
} catch (Exception e) {
e.printStackTrace();
}
return client;
}
}
後續需要使用es客戶端TransportClient 時直接使用@Autowired注入即可。
Document APIS
主要體現爲文檔的增刪改查、批量操作等
Index API
index api表現爲新增文檔(如果索引不存在會直接新建),參數需要指定索引名稱、類型、json數據三個參數,然後根據返回IndexResponse的狀態判斷是否新增成功:
@Autowired
private TransportClient transportClient;
@Autowired
private ObjectMapper objectMapper;
/**
* 新增文檔
*
* @param indexTemplate
* @return
*/
@Override
public String addIndex(MovieIndexTemplate indexTemplate) {
if (indexTemplate == null) {
return ResultUtil.fail();
}
try {
IndexRequestBuilder indexRequestBuilder = transportClient.prepareIndex(INDEX, TYPE).setSource(objectMapper.writeValueAsBytes(indexTemplate), XContentType.JSON);
IndexResponse indexResponse = indexRequestBuilder.get();
if (indexResponse.status() == RestStatus.CREATED) {
logger.info("name={},新增文檔成功", indexTemplate.getName());
return ResultUtil.success();
}
} catch (JsonProcessingException e) {
logger.error("");
}
return ResultUtil.fail();
}
@Autowired
private MovieSearchService movieSearchService;
@Test
public void testAddDocument() {
MovieIndexTemplate indexTemplate = new MovieIndexTemplate();
indexTemplate.setName("喜劇之王");
indexTemplate.setAlias("喜劇之王");
indexTemplate.setActors("周星馳,莫文蔚,張柏芝,吳孟達,林子善,田啓文");
indexTemplate.setDirectors("周星馳 李力持");
indexTemplate.setIntroduction("《喜劇之王》是星輝海外有限公司出品的一部喜劇電影,由李力持、周星馳執導,周星馳、 莫文蔚、張柏芝等主演。該片於1999年2月13日在香港上映。影片講述對喜劇情有獨鍾的尹天仇與舞女柳飄飄逐漸產生感情,之後在杜娟兒的幫助下,尹天仇終於獲得機會演主角,但又陷入與柳飄飄、杜娟兒的三角戀漩渦之中");
indexTemplate.setArea("內地");
indexTemplate.setScore(9.4f);
indexTemplate.setLabel("喜劇 愛情");
indexTemplate.setRelease("1999");
movieSearchService.addIndex(indexTemplate);
}
Get API
get api用於獲取指定的文檔數據,接收索引名稱、類型、id三個參數:
@Override
public String getIndex(String id) {
GetResponse getResponse = transportClient.prepareGet(INDEX, TYPE, id).get();
String result = getResponse.getSourceAsString();
logger.info("get api result ={},", result);
if (StringUtils.isEmpty(result)) {
return ResultUtil.fail();
}
return ResultUtil.success(result);
}
@Test
public void getIndex(){
String id = "hTCYWmgBBHn7EfncQOq6";
movieSearchService.getIndex(id);
}
get api只支持一個id,但是你可以使用multi get api來一次性傳入多個id,並返回多個對應的結果集:
//multi getindex
public void getIndex2(String[] keyword) {
MultiGetResponse multiGetItemResponse = transportClient.prepareMultiGet()
.add(INDEX, TYPE, keyword[0])
.add(INDEX, TYPE, keyword[1])
.get();
for (MultiGetItemResponse getItemResponse : multiGetItemResponse) {
GetResponse response = getItemResponse.getResponse();
String json = response.getSourceAsString();
}
}
Delete API
delete api用於刪除指定的文檔數據,同get api類似,也是接收索引名稱、類型、id三個參數:
@Override
public String deleteIndex(String id) {
DeleteRequestBuilder deleteRequestBuilder = transportClient.prepareDelete(INDEX, TYPE, id);
DeleteResponse deleteResponse = deleteRequestBuilder.get();
if (deleteResponse.status() == RestStatus.OK) {
logger.info("");
return ResultUtil.success();
}
return ResultUtil.fail();
}
@Test
public void deleteIndex(){
String id = "hTCYWmgBBHn7EfncQOq6";
movieSearchService.deleteIndex(id);
}
Delete By Query API
這個刪除操作根據篩選條件來刪除指定數據,比單純的根據id進行刪除靈活一些,例如下面的根據電影name字段匹配刪除:
@Override
public String deleteByQueryAction(String keyword) {
BulkByScrollResponse bulkByScrollResponse = DeleteByQueryAction.INSTANCE.newRequestBuilder(transportClient).filter(QueryBuilders.matchQuery(MovieSearch.NAME, keyword)).source(INDEX).get();
long deleted = bulkByScrollResponse.getDeleted();
logger.info("根據條件keyword={}刪除result={}", keyword, deleted);
if (deleted < 1) {
logger.info("根據條件keyword={}刪除失敗", keyword);
return ResultUtil.fail();
}
logger.info("根據條件keyword={}刪除成功", keyword);
return ResultUtil.success();
}
@Test
public void deleteByQueryAction(){
String name = "喜劇之王";
movieSearchService.deleteByQueryAction(name);
}
由於這個delete by query操作可能執行較長時間,因此你可以設置異步方式執行替代直接get,並設置這個delete by query操作的監聽器,在監聽器裏面處理成功和失敗的具體邏輯:
//異步執行delete by query
public void deleteByQueryAction1(String keyword) {
DeleteByQueryAction.INSTANCE.newRequestBuilder(transportClient).filter(QueryBuilders.matchQuery(MovieSearch.NAME, keyword)).source(INDEX).execute(new ActionListener<BulkByScrollResponse>() {
@Override
public void onResponse(BulkByScrollResponse bulkByScrollResponse) {
long deleted = bulkByScrollResponse.getDeleted();
logger.info("根據條件keyword={}刪除result={}", keyword, deleted);
if (deleted < 1) {
logger.info("根據條件keyword={}刪除失敗", keyword);
}
logger.info("根據條件keyword={}刪除成功", keyword);
}
@Override
public void onFailure(Exception e) {
// Hanlder the exception……
}
});
}
Bulk API
bulk api支持批量操作,例如一個請求裏面同時包含刪除、新增功能:
//批量bulk操作
@Override
public void buldOption(MovieIndexTemplate indexTemplate, String id) throws JsonProcessingException {
BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
bulkRequestBuilder.add(transportClient.prepareDelete(INDEX, TYPE, id));
bulkRequestBuilder.add(transportClient.prepareIndex(INDEX, TYPE).setSource(objectMapper.writeValueAsBytes(indexTemplate), XContentType.JSON));
BulkResponse bulkItemResponses = bulkRequestBuilder.get();
RestStatus status = bulkItemResponses.status();
logger.info("bulk option result status={}", status);
}
Update API和Reindex API這裏不講,用的不多,由於在elastic search中不推薦對Index進行修改,而應該直接刪除再新增的方式,所以update和reindex api用的並不多。
Query DSL Java API
之前在Elastic Search之Search API(Query DSL)、字段類查詢、複合查詢 一文中說到了es的原生Query DSL API,分爲字段類查詢和複合查詢,字段類查詢又可以分爲單詞類查詢和全文匹配。
全文匹配:包括match、match_phrase、query_string、simple_query_string等查詢語句;
//Match Query
MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(MovieSearch.NAME, "毒液");
//Muitl Match Query
MultiMatchQueryBuilder multiMatchQueryBuilder = QueryBuilders.multiMatchQuery("", MovieSearch.NAME, MovieSearch.AREA, MovieSearch.INTRODUCTION, MovieSearch.ACTORS, MovieSearch.DIRECTORS);
//Common Terms Query
CommonTermsQueryBuilder commonTermsQueryBuilder = QueryBuilders.commonTermsQuery(MovieSearch.NAME, "毒液");
//Query String Query
QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery("毒液").field(MovieSearch.NAME).defaultOperator(Operator.OR);
//Simple Query String Query
SimpleQueryStringBuilder simpleQueryStringBuilder = QueryBuilders.simpleQueryStringQuery("毒液").field(MovieSearch.NAME);
單詞匹配:包括term、terms、range等查詢語句;
//Term Query
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(MovieSearch.NAME, "毒液");
//Terms Query
TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(MovieSearch.NAME, "毒液", "我不是藥神");
//Range Query 篩選評分在7~10分之間的數據集,includeLower(false)表示from是gt,反之;includeUpper(false)表示to是lt,反之
QueryBuilders.rangeQuery(MovieSearch.SCORE).from(7).to(10).includeLower(false).includeUpper(false);
Compound Query
複合查詢是es中用的最多的,常見的是Bool查詢,包括must、should、filter、must_not,在Elastic Search之Search API(Query DSL)、字段類查詢、複合查詢 一文中也有說到過其原生api。下面是綜合使用Bool查詢示例code:
public void boolDsl() {
//Bool Query
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//電影名稱必須包含我不是藥神經過分詞後的文本,比如我、不、是、藥、神
boolQueryBuilder.must(QueryBuilders.matchQuery(MovieSearch.NAME, "我不是藥神"));
//排除導演是張三的電影信息
boolQueryBuilder.mustNot(QueryBuilders.termQuery(MovieSearch.DIRECTORS, "張三"));
//別名中應該包含藥神經過分詞後的文本,比如藥、神
boolQueryBuilder.should(QueryBuilders.matchQuery(MovieSearch.ALIAS, "藥神"));
//評分必須大於9(因爲es對filter會有智能緩存,推薦使用)
boolQueryBuilder.filter(QueryBuilders.rangeQuery(MovieSearch.SCORE).gt(9));
//name、actors、introduction、alias、label 多字段匹配"藥神",或的關係
boolQueryBuilder.filter(QueryBuilders.multiMatchQuery("藥神", MovieSearch.NAME, MovieSearch.ACTORS, MovieSearch.INTRODUCTION, MovieSearch.ALIAS, MovieSearch.LABEL));
String[] includes = {MovieSearch.NAME, MovieSearch.ALIAS, MovieSearch.SCORE, MovieSearch.ACTORS, MovieSearch.DIRECTORS, MovieSearch.INTRODUCTION};
SearchRequestBuilder searchRequestBuilder = transportClient.prepareSearch(INDEX).setTypes(TYPE).setQuery(boolQueryBuilder).addSort(MovieSearch.SCORE, SortOrder.DESC).setFrom(0).setSize(10).setFetchSource(includes, null);
SearchResponse searchResponse = searchRequestBuilder.get();
if (!RestStatus.OK.equals(searchResponse.status())) {
return;
}
for (SearchHit searchHit : searchResponse.getHits()) {
String name = (String) searchHit.getSource().get(MovieSearch.NAME);
//TODO
}
}
根據這些基礎api做了個基於es搜索引擎的demo項目,功能比較簡單,就是可以根據用戶輸入的文本去es中實時搜索數據集,效果如下圖:
項目github:https://github.com/simonsfan/springboot-quartz-demo,分支:feature_es。要注意:由於這個項目之前是用來展示 "使用quartz實現定製化定時任務" 功能的,所以需要kafka服務,並且要安裝elastic search服務。項目中有很多問題需要完善,有興趣的小夥伴可以自己完善下。有問題歡迎留言交流!
上一篇: Elastic Search之分頁展示
下一篇: Elastic Search與mysql數據同步方案
參考資料: es官網https://www.elastic.co/guide/en/elasticsearch/client/java-api/6.3/index.html