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

前边我们学习了如何使用 RESTful API 去操作 ES,这种方式可能在实际项目中用的比较少,但这些内容都是必备的基础,对后续的学习还是很有帮助的,还是需要掌握的。

Elasticsearch Clients 提供了许多语言的支持,我们要学习其中的 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、添加依赖

这里我们创建一个 Spring Boot 项目,添加如下依赖来引入 Java High Level REST Client:

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

由于 ES 的版本更新比较快,基本每个月会更新1-2个版本,但 Spring Data Elasticsearch 对 ES 新版本的支持还是比较滞后的,如果 Spring Data Elasticsearch 对应的 ES 版本比你安装的 ES 版本低,建议直接修改 Spring Data Elasticsearch 对应的 ES 版本,使其和你安装的 ES 版本保持一致。

我创建的基于 Spring Boot 2.3.6.RELEASE 的项目,其中的 ES 版本为7.6.2:


我之前安装的是 ES7.9.3 版本,所以可通过如下方式修改 Spring Data Elasticsearch 对应的 ES 版本号(目前最新的 Spring Boot 2.4.0 已经支持 ES7.9.3 了):

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

通过 Spring Data Elasticsearch 来集成 Java High Level REST Client,就可以使用 Spring Data 的一些特性来简化开发,但前期我们先学习 Java High Level REST Client 的原生 API,这个更具有通用性,掌握了原生 API 再学习整合 Spring Data 后的一些特性就很容易了。

由于还会用到 JSON 相关的操作,这里添加fastjson依赖:

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

2、初始化

创建如下配置类,来连接到 ES 节点,创建RestHighLevelClient对象,这样初始化工作就完成了。RestHighLevelClient是重点,后续的各种操作都要使用它:

@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;
    }
}

我们先创建一个UserService类,里边注入通过配置类生成的RestHighLevelClient对象,所有的操作的方法都在该类里完成:

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

3、创建索引

如下的代码会创建一个名为user的索引:

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

创建索引时也可以根据需要指定一些配置信息,例如分片数量、文档字段的映射信息、索引别名等:

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

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

    // 设置文档字段的映射信息
    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设置文档字段的映射信息
//    request.mapping("{\n" +
//            "   \"properties\": {\n" +
//            "       \"birthday\": {\n" +
//            "           \"type\": \"date\",\n" +
//            "           \"format\": \"yyyy-MM-dd\"\n" +
//            "       }\n" +
//            "   }\n" +
//            "}", XContentType.JSON);

    // 设置索引别名
    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、添加文档

创建好了索引就可以给里边添加文档数据了,首先看添加单个文档:

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));
    // 超时时间
    request.timeout("2s");
    // 文档id
    request.id("1");
    // 设置要添加的数据
    request.source(JSON.toJSONString(user), XContentType.JSON);
    IndexResponse response = client.index(request, RequestOptions.DEFAULT);
    System.out.println(response.status());
}

我们这里以将对象转成 JSON 串再添加,这种相对简单通用些。添加时如果不提供文档 id,ES 会给一个默认值,这里为了方便后边演示就指定了文档 id。

官方还提供了其它方式,可以查看文档

如果需要批量添加我们可以使用BulkRequest类来完成,具体的实现如下:

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("复旦");

        User user4 = new User();
        user4.setName("张三丰");
        user4.setAge(80);
        user4.setBirthday("1940-08-15");
        user4.setSchool("复旦");

        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());
    }

接下来通过单元测试创建索引、添加文档:

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

    @Autowired
    UserService userService;

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

最终在 head 工具中可以看到如下数据:


5、修改文档

可以根据文档 id 修改文档,修改前可以判断文档是否存在:

public boolean existsDocument() throws IOException {
    GetRequest request = new GetRequest("user", "1");
    // 不获取_source的内容
    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());
}

根据文档 id 修改的局限性还是太强了,我们还可以使用UpdateByQueryRequest根据查询条件来批量修改文档:

public void updateDocument2() throws IOException {
    UpdateByQueryRequest request = new UpdateByQueryRequest("user");
    // 设置查询条件
    request.setQuery(new MatchPhraseQueryBuilder("name", "张三"));
    // request.setQuery(new TermQueryBuilder("name.keyword", "张三"));
    // 设置一次可以批处理的文档数,默认1000
    request.setBatchSize(200);
    // 更新后刷新索引
    request.setRefresh(true);
    // 通过脚本设置如何更新
    request.setScript(new Script("ctx._source.school = '复旦'"));
    BulkByScrollResponse response = client.updateByQuery(request, RequestOptions.DEFAULT);
    System.out.println("修改的文档数:" + response.getStatus().getUpdated());
}

6、删除文档

首先可以根据文档 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");
    // 设置查询条件,查询school是复旦的
    request.setQuery(new TermQueryBuilder("school.keyword", "复旦"));
    // request.setQuery(new MatchPhraseQueryBuilder("school", "复旦"));
    // 设置一次可以批处理的文档数,默认1000
    request.setBatchSize(200);
    // 更新后刷新索引
    request.setRefresh(true);
    BulkByScrollResponse response = client.deleteByQuery(request, RequestOptions.DEFAULT);
    System.out.println("删除的文档数:" + response.getStatus().getDeleted());
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容