Elasticsearch 使用 Java High Level REST Client 操作索引、文檔

前邊我們學(xué)習(xí)了如何使用 RESTful API 去操作 ES,這種方式可能在實(shí)際項(xiàng)目中用的比較少,但這些內(nèi)容都是必備的基礎(chǔ),對(duì)后續(xù)的學(xué)習(xí)還是很有幫助的,還是需要掌握的。

Elasticsearch Clients 提供了許多語言的支持,我們要學(xué)習(xí)其中的 Java REST Client,通過編寫 Java 代碼的方式來操作 ES。


其中 Java REST Client 又分為 Java Low Level REST Client 和 Java High Level REST Client,我們要使用的是 Java High Level REST Client:

注意,Java High Level REST Client 最低需要 Java1.8 的版本。

1、添加依賴

這里我們創(chuàng)建一個(gè) Spring Boot 項(xiàng)目,添加如下依賴來引入 Java High Level REST Client:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

由于 ES 的版本更新比較快,基本每個(gè)月會(huì)更新1-2個(gè)版本,但 Spring Data Elasticsearch 對(duì) ES 新版本的支持還是比較滯后的,如果 Spring Data Elasticsearch 對(duì)應(yīng)的 ES 版本比你安裝的 ES 版本低,建議直接修改 Spring Data Elasticsearch 對(duì)應(yīng)的 ES 版本,使其和你安裝的 ES 版本保持一致。

我創(chuàng)建的基于 Spring Boot 2.3.6.RELEASE 的項(xiàng)目,其中的 ES 版本為7.6.2:


我之前安裝的是 ES7.9.3 版本,所以可通過如下方式修改 Spring Data Elasticsearch 對(duì)應(yīng)的 ES 版本號(hào)(目前最新的 Spring Boot 2.4.0 已經(jīng)支持 ES7.9.3 了):

<properties>
    <elasticsearch.version>7.9.3</elasticsearch.version>
</properties>

通過 Spring Data Elasticsearch 來集成 Java High Level REST Client,就可以使用 Spring Data 的一些特性來簡化開發(fā),但前期我們先學(xué)習(xí) Java High Level REST Client 的原生 API,這個(gè)更具有通用性,掌握了原生 API 再學(xué)習(xí)整合 Spring Data 后的一些特性就很容易了。

由于還會(huì)用到 JSON 相關(guān)的操作,這里添加fastjson依賴:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.73</version>
</dependency>

2、初始化

創(chuàng)建如下配置類,來連接到 ES 節(jié)點(diǎn),創(chuàng)建RestHighLevelClient對(duì)象,這樣初始化工作就完成了。RestHighLevelClient是重點(diǎn),后續(xù)的各種操作都要使用它:

@Configuration
public class ElasticsearchConfig {
    @Bean
    public RestHighLevelClient restHighLevelClient() {
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("127.0.0.1", 9200, "http"),
                        new HttpHost("127.0.0.1", 9201, "http"),
                        new HttpHost("127.0.0.1", 9202, "http")));
        return client;
    }
}

我們先創(chuàng)建一個(gè)UserService類,里邊注入通過配置類生成的RestHighLevelClient對(duì)象,所有的操作的方法都在該類里完成:

@Service
public class UserService {
    @Autowired
    @Qualifier("restHighLevelClient")
    private RestHighLevelClient client;
}

3、創(chuàng)建索引

如下的代碼會(huì)創(chuàng)建一個(gè)名為user的索引:

public void createIndex() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest("user");
    CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
    System.out.println(response.isAcknowledged());
}

創(chuàng)建索引時(shí)也可以根據(jù)需要指定一些配置信息,例如分片數(shù)量、文檔字段的映射信息、索引別名等:

public void createIndex2() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest("user");

    // 索引分片數(shù)量配置
    request.settings(Settings.builder()
            .put("index.number_of_shards", 2)
            .put("index.number_of_replicas", 1));

    // 設(shè)置文檔字段的映射信息
    Map<String, Object> birthday = new HashMap<>();
    birthday.put("type", "date");
    birthday.put("format", "yyyy-MM-dd");
    Map<String, Object> properties = new HashMap<>();
    properties.put("birthday", birthday);
    Map<String, Object> mapping = new HashMap<>();
    mapping.put("properties", properties);
    request.mapping(mapping);
        
    // 通過json設(shè)置文檔字段的映射信息
//    request.mapping("{\n" +
//            "   \"properties\": {\n" +
//            "       \"birthday\": {\n" +
//            "           \"type\": \"date\",\n" +
//            "           \"format\": \"yyyy-MM-dd\"\n" +
//            "       }\n" +
//            "   }\n" +
//            "}", XContentType.JSON);

    // 設(shè)置索引別名
    request.alias(new Alias("user_alias"));

    CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
    System.out.println(response.isAcknowledged());
}

3、刪除索引

刪除索引前可以先判斷索引是否存在:

public boolean existsIndex() throws IOException {
    GetIndexRequest request = new GetIndexRequest("user");
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    return exists;
}

然后再刪除:

public void deleteIndex() throws IOException {
    if (!existsIndex()) {
        return;
    }
    DeleteIndexRequest request = new DeleteIndexRequest("user");
    AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
    System.out.println(response.isAcknowledged());
}

4、添加文檔

創(chuàng)建好了索引就可以給里邊添加文檔數(shù)據(jù)了,首先看添加單個(gè)文檔:

public void addDocument() throws IOException {
    User user = new User();
    user.setName("張三");
    user.setAge(30);
    user.setBirthday("1990-03-12");
    user.setSchool("清華");

    IndexRequest request = new IndexRequest("user");
//    request.timeout(TimeValue.timeValueSeconds(2));
    // 超時(shí)時(shí)間
    request.timeout("2s");
    // 文檔id
    request.id("1");
    // 設(shè)置要添加的數(shù)據(jù)
    request.source(JSON.toJSONString(user), XContentType.JSON);
    IndexResponse response = client.index(request, RequestOptions.DEFAULT);
    System.out.println(response.status());
}

我們這里以將對(duì)象轉(zhuǎn)成 JSON 串再添加,這種相對(duì)簡單通用些。添加時(shí)如果不提供文檔 id,ES 會(huì)給一個(gè)默認(rèn)值,這里為了方便后邊演示就指定了文檔 id。

官方還提供了其它方式,可以查看文檔。

如果需要批量添加我們可以使用BulkRequest類來完成,具體的實(shí)現(xiàn)如下:

public void bulkAddDocument() throws IOException {
        User user1 = new User();
        user1.setName("李四");
        user1.setAge(18);
        user1.setBirthday("2002-01-08");
        user1.setSchool("北大");

        User user2 = new User();
        user2.setName("王五");
        user2.setAge(25);
        user2.setBirthday("1995-02-05");
        user2.setSchool("北大");

        User user3 = new User();
        user3.setName("趙六");
        user3.setAge(43);
        user3.setBirthday("1977-04-03");
        user3.setSchool("復(fù)旦");

        User user4 = new User();
        user4.setName("張三豐");
        user4.setAge(80);
        user4.setBirthday("1940-08-15");
        user4.setSchool("復(fù)旦");

        User user5 = new User();
        user5.setName("王重陽");
        user5.setAge(70);
        user5.setBirthday("1950-07-07");
        user5.setSchool("清華");

        Object[] users = new Object[]{user1, user2, user3, user4, user5};

        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("5s");

        for (int i = 0; i < users.length; i++) {
            String id = String.valueOf(i + 2);
            String source = JSON.toJSONString(users[i]);
            bulkRequest.add(new IndexRequest("user").id(id).source(source, XContentType.JSON));
        }

        BulkResponse responses = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(responses.status());
    }

接下來通過單元測試創(chuàng)建索引、添加文檔:

@RunWith(SpringRunner.class)
@SpringBootTest
class LearnElasticsearchApplicationTests {

    @Autowired
    UserService userService;

    @Test
    void testES() throws IOException {
        userService.createIndex2();
        userService.addDocument();
        userService.bulkAddDocument();
    }
}

最終在 head 工具中可以看到如下數(shù)據(jù):


5、修改文檔

可以根據(jù)文檔 id 修改文檔,修改前可以判斷文檔是否存在:

public boolean existsDocument() throws IOException {
    GetRequest request = new GetRequest("user", "1");
    // 不獲取_source的內(nèi)容
    request.fetchSourceContext(new FetchSourceContext(false));
    // 不獲取已排序字段
    request.storedFields("_none_");
    boolean exists = client.exists(request, RequestOptions.DEFAULT);
    return exists;
}

修改 id 為 1 的文檔的age字段值:

public void updateDocument() throws IOException {
    if (!existsDocument()) {
        return;
    }
    UpdateRequest request = new UpdateRequest("user", "1");
    User user = new User();
    user.setAge(31);
    updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
    UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
    System.out.println(response.status());
}

根據(jù)文檔 id 修改的局限性還是太強(qiáng)了,我們還可以使用UpdateByQueryRequest根據(jù)查詢條件來批量修改文檔:

public void updateDocument2() throws IOException {
    UpdateByQueryRequest request = new UpdateByQueryRequest("user");
    // 設(shè)置查詢條件
    request.setQuery(new MatchPhraseQueryBuilder("name", "張三"));
    // request.setQuery(new TermQueryBuilder("name.keyword", "張三"));
    // 設(shè)置一次可以批處理的文檔數(shù),默認(rèn)1000
    request.setBatchSize(200);
    // 更新后刷新索引
    request.setRefresh(true);
    // 通過腳本設(shè)置如何更新
    request.setScript(new Script("ctx._source.school = '復(fù)旦'"));
    BulkByScrollResponse response = client.updateByQuery(request, RequestOptions.DEFAULT);
    System.out.println("修改的文檔數(shù):" + response.getStatus().getUpdated());
}

6、刪除文檔

首先可以根據(jù)文檔 id 來刪除文檔:

public void deleteDocument() throws IOException {
    if (!existsDocument()) {
        return;
    }
    DeleteRequest request = new DeleteRequest("user", "1");
    DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
    System.out.println(response.status());
}

和修改類似,我們也可以使用DeleteByQueryRequest批量刪除符合指定查詢條件的文檔:

public void deleteDocument2() throws IOException {
    DeleteByQueryRequest request = new DeleteByQueryRequest("user");
    // 設(shè)置查詢條件,查詢school是復(fù)旦的
    request.setQuery(new TermQueryBuilder("school.keyword", "復(fù)旦"));
    // request.setQuery(new MatchPhraseQueryBuilder("school", "復(fù)旦"));
    // 設(shè)置一次可以批處理的文檔數(shù),默認(rèn)1000
    request.setBatchSize(200);
    // 更新后刷新索引
    request.setRefresh(true);
    BulkByScrollResponse response = client.deleteByQuery(request, RequestOptions.DEFAULT);
    System.out.println("刪除的文檔數(shù):" + response.getStatus().getDeleted());
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容