前言
項目中使用到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();
}
}
}
}
Spring整合以及深入配置
// SpringContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:property-placeholder file-encoding="UTF-8"
location="classpath:elasticSearch.properties" ignore-unresolvable="true"/>
<import resource="spring-esclient.xml"/>
<bean class="com.xx.product.tools.SpringBeanFactory"/>
<aop:aspectj-autoproxy expose-proxy="true"/>
<bean class="com.xx.product.aspect.ElasticSearchAspect"/>
</beans>
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import com.xx.es.elasticSearch.ESClientSpringFactory;
//使用javaConfig方式配置
@Configuration
@ComponentScan(basePackageClasses=ESClientSpringFactory.class)
public class ESConfig {
@Value("${httpHost.host}")
private String host;
@Value("${httpHost.port}")
private int port;
@Value("${httpHost.schema}")
private String schema;
@Value("${esclient.connectNum}")
private Integer connectNum;
@Value("${esclient.connectPerRoute}")
private Integer connectPerRoute;
@Bean
public HttpHost httpHost(){
return new HttpHost(host,port,schema);
}
@Bean(initMethod="init",destroyMethod="close")
public ESClientSpringFactory getFactory(){
return ESClientSpringFactory.
build(httpHost(), connectNum, connectPerRoute);
}
@Bean
@Scope("singleton")
public RestClient getRestClient(){
return getFactory().getClient();
}
@Bean
@Scope("singleton")
public RestHighLevelClient getRHLClient(){
return getFactory().getRhlClient();
}
}
// elasticSearch.properties
httpHost.host=xx.xx.xx.xx
httpHost.port=9200
httpHost.schema=http
esclient.connectNum=50
esclient.connectPerRoute=10
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"
}
}
}
}
}
使用Java客戶端創建索引,請轉到 ElasticSearch RestHighLevelClient 教程(二) 操作index
插入數據
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();
}
}
}
查詢結果:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 2.1507285,
"hits": [
{
"_index": "demo",
"_type": "demo",
"_id": "AWE1fnSx00f4t28WJ4D6",
"_score": 2.1507285,
"_source": {
"title": "費德勒收鄭泫退賽禮 進決賽戰西里奇"
}
}
]
}
}
更新文檔
如果插入了錯誤的數據,想要更改或者在文檔中新增新的數據,那麼就需要更新文檔了。
演示 將費德勒的新聞的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文檔更改,至於腳本更改以後有機會再介紹。
刪除文檔
此處參見文章
ElasticSearch RestHighLevelClient 教程(三) 刪除&&查詢刪除
總結
以上就是筆者使用ES Java客戶端的部分demo,希望以上代碼可以幫助到讀者也能爲以後再次使用時提供幫助。
系列文章:
ElasticSearch RestHighLevelClient 教程(二) 操作index
ElasticSearch RestHighLevelClient 教程(三) 刪除&&查詢刪除