項目中使用到ElasticSearch作為搜索引擎。而ES的環境搭建自然是十分簡單,且本身就適應於分布式環境,因此這塊就不多贅述。而其本身特性和查詢語句這篇博文不會介紹,如果有機會會深入介紹。
所以這篇博文主要還是介紹Java客戶端中如何使用查詢搜索引擎中的數據。而使用的Java客戶端是官方新推出的RestHighLevelClient,使用Http連接查詢結果。但是網上相關資料較少,只有官網的api介紹。所以本文以一個小demo介紹RestHighLevelClient的使用。
項目依賴:
dependencies {
// https://mvnrepository.com/artifact/org.elasticsearch.client/elasticsearch-rest-high-level-client
compile group: 'org.elasticsearch.client', name: 'elasticsearch-rest-high-level-client', version: '5.6.2'
}
一般配置Java Client
// Java客戶端生成工廠 public class ESClientFactory { private static final String HOST = "127.0.0.1"; private static final int PORT = 9200; private static final String SCHEMA = "http"; private static final int CONNECT_TIME_OUT = 1000; private static final int SOCKET_TIME_OUT = 30000; private static final int CONNECTION_REQUEST_TIME_OUT = 500; private static final int MAX_CONNECT_NUM = 100; private static final int MAX_CONNECT_PER_ROUTE = 100; private static HttpHost HTTP_HOST = new HttpHost(HOST,PORT,SCHEMA); private static boolean uniqueConnectTimeConfig = false; private static boolean uniqueConnectNumConfig = true; private static RestClientBuilder builder; private static RestClient restClient; private static RestHighLevelClient restHighLevelClient; static { init(); } public static void init(){ builder = RestClient.builder(HTTP_HOST); if(uniqueConnectTimeConfig){ setConnectTimeOutConfig(); } if(uniqueConnectNumConfig){ setMutiConnectConfig(); } restClient = builder.build(); restHighLevelClient = new RestHighLevelClient(restClient); } // 主要關於異步httpclient的連接延時配置 public static void setConnectTimeOutConfig(){ builder.setRequestConfigCallback(new RequestConfigCallback() { @Override public Builder customizeRequestConfig(Builder requestConfigBuilder) { requestConfigBuilder.setConnectTimeout(CONNECT_TIME_OUT); requestConfigBuilder.setSocketTimeout(SOCKET_TIME_OUT); requestConfigBuilder.setConnectionRequestTimeout(CONNECTION_REQUEST_TIME_OUT); return requestConfigBuilder; } }); } // 主要關於異步httpclient的連接數配置 public static void setMutiConnectConfig(){ builder.setHttpClientConfigCallback(new HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) { httpClientBuilder.setMaxConnTotal(MAX_CONNECT_NUM); httpClientBuilder.setMaxConnPerRoute(MAX_CONNECT_PER_ROUTE); return httpClientBuilder; } }); } public static RestClient getClient(){ return restClient; } public static RestHighLevelClient getHighLevelClient(){ return restHighLevelClient; } public static void close() { if (restClient != null) { try { restClient.close(); } catch (IOException e) { e.printStackTrace(); } } } }
public class ESClientSpringFactory { public static int CONNECT_TIMEOUT_MILLIS = 1000; public static int SOCKET_TIMEOUT_MILLIS = 30000; public static int CONNECTION_REQUEST_TIMEOUT_MILLIS = 500; public static int MAX_CONN_PER_ROUTE = 10; public static int MAX_CONN_TOTAL = 30; private static HttpHost HTTP_HOST; private RestClientBuilder builder; private RestClient restClient; private RestHighLevelClient restHighLevelClient; private static ESClientSpringFactory esClientSpringFactory = new ESClientSpringFactory(); private ESClientSpringFactory(){} public static ESClientSpringFactory build(HttpHost httpHost, Integer maxConnectNum, Integer maxConnectPerRoute){ HTTP_HOST = httpHost; MAX_CONN_TOTAL = maxConnectNum; MAX_CONN_PER_ROUTE = maxConnectPerRoute; return esClientSpringFactory; } public static ESClientSpringFactory build(HttpHost httpHost,Integer connectTimeOut, Integer socketTimeOut, Integer connectionRequestTime,Integer maxConnectNum, Integer maxConnectPerRoute){ HTTP_HOST = httpHost; CONNECT_TIMEOUT_MILLIS = connectTimeOut; SOCKET_TIMEOUT_MILLIS = socketTimeOut; CONNECTION_REQUEST_TIMEOUT_MILLIS = connectionRequestTime; MAX_CONN_TOTAL = maxConnectNum; MAX_CONN_PER_ROUTE = maxConnectPerRoute; return esClientSpringFactory; } public void init(){ builder = RestClient.builder(HTTP_HOST); setConnectTimeOutConfig(); setMutiConnectConfig(); restClient = builder.build(); restHighLevelClient = new RestHighLevelClient(restClient); System.out.println("init factory"); } // 配置連接時間延時 public void setConnectTimeOutConfig(){ builder.setRequestConfigCallback(new RequestConfigCallback() { @Override public Builder customizeRequestConfig(Builder requestConfigBuilder) { requestConfigBuilder.setConnectTimeout(CONNECT_TIMEOUT_MILLIS); requestConfigBuilder.setSocketTimeout(SOCKET_TIMEOUT_MILLIS); requestConfigBuilder.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MILLIS); return requestConfigBuilder; } }); } // 使用異步httpclient時設置並發連接數 public void setMutiConnectConfig(){ builder.setHttpClientConfigCallback(new HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) { httpClientBuilder.setMaxConnTotal(MAX_CONN_TOTAL); httpClientBuilder.setMaxConnPerRoute(MAX_CONN_PER_ROUTE); return httpClientBuilder; } }); } public RestClient getClient(){ return restClient; } public RestHighLevelClient getRhlClient(){ return restHighLevelClient; } public void close() { if (restClient != null) { try { restClient.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("close client"); } }
兩種配置方法從本質上都是對client進行配置,且達到相同目的。
由於Client配置為單例模式,在Spring中的生命周期隨着容器開始結束而開始結束。在定義bean創建和銷毀方法后會自動關閉連接。
但是使用一般Java配置時,需要手動關閉。如果在web項目中,可以使用監聽器,隨着項目的生命周期手動調用開啟關閉。
客戶端演示
接下來就是最簡單的幾個demo,校驗這種客戶端的有效性,同時也為大家試驗如何使用這種Java客戶端:
數據准備
首先准備要操作的數據:創建一個index為demo,type為demo的新聞索引。
/PUT http://{{host}}:{{port}}/demo { "mappings":{ "demo":{ "properties":{ "title":{ "type":"text" }, "tag":{ "type":"keyword" }, "publishTime":{ "type":"date" } } } } }
插入數據
API格式
/POST http://{{host}}:{{port}}/demo/demo/ { "title":"中國產小型無人機的“對手”來了,俄微型攔截導彈便宜量又多", "tag":"軍事", "publishTime":"2018-01-24T23:59:30Z" }
Java Client
public class News { private String title; private String tag; private String publishTime; public News() { super(); } public News(String title, String tag, String publishTime) { super(); this.title = title; this.tag = tag; this.publishTime = publishTime; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getTag() { return tag; } public void setTag(String tag) { this.tag = tag; } public String getPublishTime() { return publishTime; } public void setPublishTime(String publishTime) { this.publishTime = publishTime; } }
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring/spring-context.xml") public class FreeClientTest { private String index; private String type; @Autowired private RestHighLevelClient rhlClient; @Before public void prepare() { index = "demo"; type = "demo"; } @Test public void addTest() { IndexRequest indexRequest = new IndexRequest(index, type); News news = new News(); news.setTitle("中國產小型無人機的“對手”來了,俄微型攔截導彈便宜量又多"); news.setTag("軍事"); news.setPublishTime("2018-01-24T23:59:30Z"); String source = JsonUtil.toString(news); indexRequest.source(source, XContentType.JSON); try { rhlClient.index(indexRequest); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
兩種方式均可插入數據。如果有大量數據這樣插入未免效率太低,接下來看一看批量插入數據。
批量插入數據
API格式
/POST http://{{host}}:{{port}}/_bulk {"index":{"_index":"demo","_type":"demo"}} {"title":"中印邊防軍於拉達克舉行會晤 強調維護邊境和平","tag":"軍事","publishTime":"2018-01-27T08:34:00Z"} {"index":{"_index":"demo","_type":"demo"}} {"title":"費德勒收鄭泫退賽禮 進決賽戰西里奇","tag":"體育","publishTime":"2018-01-26T14:34:00Z"} {"index":{"_index":"demo","_type":"demo"}} {"title":"歐文否認拿動手術威脅騎士 興奮全明星聯手詹皇","tag":"體育","publishTime":"2018-01-26T08:34:00Z"} {"index":{"_index":"demo","_type":"demo"}} {"title":"皇馬官方通告拉莫斯伊斯科傷情 將缺陣西甲關鍵戰","tag":"體育","publishTime":"2018-01-26T20:34:00Z"}
Java Client
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring/spring-context.xml") public class FreeClientTest { private String index; private String type; @Autowired private RestHighLevelClient rhlClient; @Before public void prepare() { index = "demo"; type = "demo"; } @Test public void batchAddTest() { BulkRequest bulkRequest = new BulkRequest(); List<IndexRequest> requests = generateRequests(); for (IndexRequest indexRequest : requests) { bulkRequest.add(indexRequest); } try { rhlClient.bulk(bulkRequest); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public List<IndexRequest> generateRequests(){ List<IndexRequest> requests = new ArrayList<>(); requests.add(generateNewsRequest("中印邊防軍於拉達克舉行會晤 強調維護邊境和平", "軍事", "2018-01-27T08:34:00Z")); requests.add(generateNewsRequest("費德勒收鄭泫退賽禮 進決賽戰西里奇", "體育", "2018-01-26T14:34:00Z")); requests.add(generateNewsRequest("歐文否認拿動手術威脅騎士 興奮全明星聯手詹皇", "體育", "2018-01-26T08:34:00Z")); requests.add(generateNewsRequest("皇馬官方通告拉莫斯伊斯科傷情 將缺陣西甲關鍵戰", "體育", "2018-01-26T20:34:00Z")); return requests; } public IndexRequest generateNewsRequest(String title, String tag, String publishTime){ IndexRequest indexRequest = new IndexRequest(index, type); News news = new News(); news.setTitle(title); news.setTag(tag); news.setPublishTime(publishTime); String source = JsonUtil.toString(news); indexRequest.source(source, XContentType.JSON); return indexRequest; } }
無論通過哪種方式,現在ES中已插入五條文檔數據。那么現在就可以通過多種多樣的查詢方式獲得需要的數據了。
查詢數據
查詢目標:2018年1月26日早八點到晚八點關於費德勒的前十條體育新聞的標題
API 格式
/POST http://{{host}}:{{port}}/demo/demo/_search { "from":"0", "size":"10", "_source":["title"], "query":{ "bool":{ "must":{ "match":{ "title":"費德勒" } }, "must":{ "term":{"tag":"體育"} }, "must":{ "range":{ "publishTime":{ "gte":"2018-01-26T08:00:00Z", "lte":"2018-01-26T20:00:00Z" } } } } } }
Java Client
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring/spring-context.xml") public class FreeClientTest { private String index; private String type; @Autowired private RestHighLevelClient rhlClient; @Before public void prepare() { index = "demo"; type = "demo"; } @Test public void queryTest(){ SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); sourceBuilder.from(0); sourceBuilder.size(10); sourceBuilder.fetchSource(new String[]{"title"}, new String[]{}); MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "費德勒"); TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("tag", "體育"); RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("publishTime"); rangeQueryBuilder.gte("2018-01-26T08:00:00Z"); rangeQueryBuilder.lte("2018-01-26T20:00:00Z"); BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery(); boolBuilder.must(matchQueryBuilder); boolBuilder.must(termQueryBuilder); boolBuilder.must(rangeQueryBuilder); sourceBuilder.query(boolBuilder); SearchRequest searchRequest = new SearchRequest(index); searchRequest.types(type); searchRequest.source(sourceBuilder); try { SearchResponse response = rhlClient.search(searchRequest); System.out.println(response); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
更新文檔
如果插入了錯誤的數據,想要更改或者在文檔中新增新的數據,那么就需要更新文檔了。
演示 將費德勒的新聞的tag
更改為網球類型:
API格式
/POST http://{{host}}:{{port}}/demo/demo/AWE1fnSx00f4t28WJ4D6/_update { "doc":{ "tag":"網球" } }
Java Client
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:spring/spring-context.xml") public class FreeClientTest { private String index; private String type; private String id; @Autowired private RestHighLevelClient rhlClient; @Before public void prepare() { index = "demo"; type = "demo"; id = "AWE1fnSx00f4t28WJ4D6"; } @Test public void updateTest(){ UpdateRequest updateRequest = new UpdateRequest(index, type, id); Map<String, String> map = new HashMap<>(); map.put("tag", "網球"); updateRequest.doc(map); try { rhlClient.update(updateRequest); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
以上介紹了最簡單的doc文檔更改
ID方式刪除
API格式
/DELETE http://{{host}}:{{port}}/delete_demo/demo/AWExGSdW00f4t28WAPen
- 1
Java 客戶端
public class ElkDaoTest extends BaseTest{ @Autowired private RestHighLevelClient rhlClient; private String index; private String type; private String id; @Before public void prepare(){ index = "delete_demo"; type = "demo"; id = "AWExGSdW00f4t28WAPeo"; } @Test public void delete(){ DeleteRequest deleteRequest = new DeleteRequest(index,type,id); DeleteResponse response = null; try { response = rhlClient.delete(deleteRequest); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(response); } }
同樣刪除成功。