elasticsearch 6.x 與elasticsearch 7.x 配置與使用(Java)

木子人弋山發表於2020-01-13

前些天看完了一本關於elasticsearch的書籍,並且做了一個elasticsearch相關專案,對與es也算是有了一定程度的瞭解,不過看書向來都是一邊看一邊忘的,以此文章記錄一些es的簡單用法。

依賴

由於本人用的es版本為es 7.2的映象,故所有依賴都是es 7.2 版本。以下為依賴程式碼。

	<dependency>
	    <groupId>org.elasticsearch</groupId>
	    <artifactId>elasticsearch</artifactId>
	    <version>7.2.0</version>
	</dependency>
	<!--這個依賴 7.x 版本可以不新增,但是由於本人程式碼用到,所以不去除-->
	<dependency>
	    <groupId>org.elasticsearch.client</groupId>
	    <artifactId>transport</artifactId>
	    <version>7.2.0</version>
	</dependency>
	<dependency>
	    <groupId>org.elasticsearch.client</groupId>
	    <artifactId>elasticsearch-rest-high-level-client</artifactId>
	    <version>7.2.0</version>
	</dependency>
	<dependency>
	    <groupId>org.elasticsearch.client</groupId>
	    <artifactId>elasticsearch-rest-client</artifactId>
	    <version>7.2.0</version>
	</dependency>

配置

下面為ElasticsearchConfig的配置程式碼

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.InetAddress;

/**
 * @Author lidai
 * @Date 2020/1/7 16:20
 */
@Configuration
public class ElasticsearchConfig {

    @Value("${elasticsearch.host}")
    private String host;

    @Value("${elasticsearch.port}")
    private int port;

    @Value("${elasticsearch.scheme}")
    private String scheme;

    @Value("${elasticsearch.timeout}")
    private int timeout;

    @Bean(name = "highLevelClient")
    public RestHighLevelClient restHighLevelClient() {
        //可以傳httpHost陣列
        RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme));
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            //設定超時
            return requestConfigBuilder.setSocketTimeout(timeout);
        });
        return new RestHighLevelClient(builder);
    }

    /**
     * es7 已廢棄 TransportClient,但是程式碼記錄中用到,故留存。7.x版本可以直接捨棄
     *
     * @return
     */
    @Bean
    public TransportClient transportClient() {
        try {
            Settings settings = Settings.builder().put("cluster.name", "elasticsearch")
                    .put("client.transport.sniff", true)
                    .build();
            TransportClient transportClient = new PreBuiltTransportClient(settings);
            TransportAddress address = new TransportAddress(InetAddress.getByName(host), port);
            transportClient.addTransportAddress(address);
            return transportClient;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

工具類

es 6.x中好像在儲存或修改時不能直接使用json字串了(我記得是這樣,如果能用就更好了),所以需要使用XContentBuilder來對欄位進行處理,以下是一個處理的工具類

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.elasticsearch.common.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.Map;

/**
 * @Author lidai
 * @Date 2020/1/7 18:14
 */
public class ElasticsearchUtils {

    /**
     * 將實體轉化為 XContentBuilder 以便儲存es
     *
     * @param xContentBuilder
     * @param object
     * @return
     */
   public static XContentBuilder objectToXContentBuilder(XContentBuilder xContentBuilder, Object object) {
        try {
            JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(object));
            xContentBuilder.startObject();
            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
                xContentBuilder.field(entry.getKey(), entry.getValue());
            }
            xContentBuilder.endObject();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return xContentBuilder;
    }
}

示例程式碼

以下為增刪改查的Java示例程式碼 ,目前程式碼都是es6.x版本,7.x版本有時間在加上去。值得注意的是es7.x中 TransportClient類已經過期。所以以下示例程式碼中凡是使用TransportClient的示例程式碼均為 elasticsearch 6.x 版本。

import com.alibaba.fastjson.JSONObject;
import com.demo.interview.es.domain.ElasticsearchPage;
import com.demo.interview.es.domain.Person;
import com.demo.interview.es.utils.ElasticsearchUtils;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeRequest;
import org.elasticsearch.action.admin.indices.analyze.AnalyzeResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * @Author lidai
 * @Date 2020/1/7 18:12
 */
@Slf4j
@RestController
@RequestMapping("/elasticsearch")
public class ElasticsearchController {

    static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    static DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    static Person person = new Person(1L, "test", 20, 0, LocalDate.parse("2020-01-01").format(formatter), "備註", LocalDateTime.now().format(formatter2));
    final String DEMO_INDEX = "demo_index";
    final String DEMO_TYPE = "demo_type";

    @Autowired
    @Qualifier(value = "highLevelClient")
    private RestHighLevelClient highLevelClient;

    @Autowired
    private TransportClient transportClient;

    /**
     * 新增
     *
     * @param person
     * @throws Exception
     */
    public void createIndex(Person person) throws Exception {
        XContentBuilder xContentBuilder = XContentFactory.jsonBuilder();
        xContentBuilder = ElasticsearchUtils.objectToXContentBuilder(xContentBuilder, person);
        //建立索引
        IndexResponse indexResponse = transportClient.prepareIndex(DEMO_INDEX, DEMO_TYPE, person.getId().toString())
                .setSource(xContentBuilder)
                //立即生效,無此需求可以不設定
                .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
                .execute().actionGet();
    }

    /**
     * 修改
     *
     * @param object
     * @throws IOException
     */
    public void updateIndex(Person person) throws IOException {
        XContentBuilder xContentBuilder = XContentFactory.jsonBuilder();
        xContentBuilder = ElasticsearchUtils.objectToXContentBuilder(xContentBuilder, person);
        UpdateResponse updateResponse = transportClient.prepareUpdate(DEMO_INDEX, DEMO_TYPE, person.getId().toString())
                .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
                .setDoc(xContentBuilder).get();
    }

    /**
     * 批量修改
     *
     * @param ids
     * @throws IOException
     */
    public void batchUpdate(String[] ids) throws IOException {
        BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
        for (String id : ids) {
            bulkRequestBuilder.add(transportClient.prepareUpdate(DEMO_INDEX, DEMO_TYPE, id)
                    .setDoc(XContentFactory.jsonBuilder().startObject().field("deleted", 1).endObject()));
        }
        BulkResponse response = bulkRequestBuilder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).get();
        if (response.hasFailures()) {
            throw new ElasticsearchException(response.buildFailureMessage());
        }
    }

    /**
     * 批量新增
     *
     * @param persons
     * @throws IOException
     */
    public void batchInsert(List<Person> persons) throws IOException {
        XContentBuilder xContentBuilder = XContentFactory.jsonBuilder();
        BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
        //新增es
        for (Person person : persons) {
            //注意批量操作時資料為null會報異常
            bulkRequestBuilder.add(transportClient.prepareIndex(DEMO_INDEX, DEMO_TYPE, person.getId().toString())
                    .setSource(ElasticsearchUtils.objectToXContentBuilder(xContentBuilder, person)));
        }
        BulkResponse responses = bulkRequestBuilder.get();
        if (responses.hasFailures()) {
            throw new ElasticsearchException(responses.buildFailureMessage());
        }
    }

    /**
     * 搜尋示例
     *
     * @param params
     * @return
     */
    public ElasticsearchPage<Person> searchDemo(Map<String, Object> params) {
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        boolQueryBuilder.must(QueryBuilders.termQuery("deleted", 0));
        String keyword = (String) params.get("keyword");
        Integer news = (Integer) params.get("news");
        Integer status = (Integer) params.get("status");
        Integer columnCategory = (Integer) params.get("columnCategory");
        String year = (String) params.get("year");
        Integer pageSize = (Integer) params.get("pageSize");
        Integer pageNum = (Integer) params.get("pageNum");
        String[] messageColumn = {"messageTitle", "messageSource", "content", "tags", "issuer"};
        //keyword欄位類別搜尋
        if (news != null && StringUtils.hasText(keyword)) {
            switch (news) {
                case 1:
                    boolQueryBuilder.must(QueryBuilders.multiMatchQuery(keyword, messageColumn));
                    break;
                case 2:
                    boolQueryBuilder.must(QueryBuilders.matchQuery("messageTitle", keyword));
                    break;
                case 3:
                    boolQueryBuilder.must(QueryBuilders.matchQuery("content", keyword));
                    break;
                case 4:
                    boolQueryBuilder.must(QueryBuilders.matchQuery("messageSource", keyword));
                    break;
                case 5:
                    boolQueryBuilder.must(QueryBuilders.matchQuery("tags", keyword));
                    break;
                case 6:
                    boolQueryBuilder.must(QueryBuilders.matchQuery("issuer", keyword));
                    break;
                default:
                    break;
            }
        }
        //釋出狀態搜尋
        if (status != null) {
            switch (status) {
                case 1:
                    boolQueryBuilder.must(QueryBuilders.matchAllQuery());
                    break;
                case 2:
                    boolQueryBuilder.must(QueryBuilders.termQuery("status", 2));
                    break;
                case 3:
                    boolQueryBuilder.must(QueryBuilders.termQuery("status", 3));
                    break;
                default:
                    break;
            }
        }
        //欄目類別搜尋
        if (columnCategory != null) {
            switch (columnCategory) {
                case 1:
                    boolQueryBuilder.must(QueryBuilders.matchAllQuery());
                    break;
                case 2:
                    boolQueryBuilder.must(QueryBuilders.termQuery("columnCategory", 2));
                    break;
                case 3:
                    boolQueryBuilder.must(QueryBuilders.termQuery("columnCategory", 3));
                    break;
                default:
                    break;
            }
        }
        //年份搜尋,多年分傳參形式為{"2016","2017","2018","2019","2020"}
        if (StringUtils.hasText(year)) {
            try {
                Integer y = Integer.valueOf(year);
                boolQueryBuilder.must(QueryBuilders.rangeQuery("updateDate").from(year + "-1-1").to(year + "-12-31"));
            } catch (NumberFormatException e) {
                String[] yearList = year.split(",");
                int max = Arrays.stream(yearList).mapToInt(Integer::valueOf).max().getAsInt();
                int min = Arrays.stream(yearList).mapToInt(Integer::valueOf).min().getAsInt();
                boolQueryBuilder.must(QueryBuilders.rangeQuery("updateDate").from(min + "-1-1").to(max + "-12-31"));
            }
        }
        SearchResponse searchResponse = transportClient.prepareSearch(DEMO_INDEX)
                .setTypes(DEMO_TYPE)
                .setQuery(boolQueryBuilder)
                .setFrom((pageNum - 1) * pageSize)
                .setSize(pageSize)
                .addSort("status", SortOrder.DESC)
                .addSort("updateDate", SortOrder.DESC)
                .addSort("id", SortOrder.DESC)
                .setExplain(true)
                .execute()
                .actionGet();
        SearchHits hits = searchResponse.getHits();
        int totalHits = (int) hits.getTotalHits().value;
        SearchHit[] searchHits = hits.getHits();
        List<Person> persons = new ArrayList<>(10);
        for (SearchHit searchHit : searchHits) {
            String sourceAsString = searchHit.getSourceAsString();
            Person person = JSONObject.parseObject(sourceAsString, Person.class);
            persons.add(person);
        }
        return ElasticsearchPage.<Person>of(pageNum, pageSize, totalHits, persons);
    }

    /**
     * 查詢分詞結果,str為要分析的字串,使用|將分詞結果隔開
     *
     * @param str
     * @return
     */
    public String queryAnalyzer(String str) {
        if (StringUtils.hasText(str)) {
            AnalyzeRequest analyzeRequest = new AnalyzeRequest(DEMO_INDEX).text(str)
                    .analyzer("ik_max_word");
            List<AnalyzeResponse.AnalyzeToken> tokens = this.transportClient.admin().indices().analyze(analyzeRequest)
                    .actionGet().getTokens();
            StringBuilder sb = new StringBuilder();
            for (AnalyzeResponse.AnalyzeToken token : tokens) {
                sb.append(token.getTerm());
                sb.append("|");
            }
            str = sb.toString().substring(0, sb.toString().length() - 1);
        }
        return str;
    }

}

以上程式碼為求方便並未測試(後續有時間會測試),有問題歡迎指出,謝謝。

另外程式碼中 7.x版本程式碼暫無,有時間再新增。

相關文章