张春 发表于 2024-5-15 07:35:48

ElasticSearch8 - SpringBoot整合ElasticSearch

媒介

springboot 整合 ES 有两种方案,ES 官方提供的 Elasticsearch Java API Client 和 spring 提供的 (Spring Data Elasticsearch)
两种方案各有优劣
Spring:高度封装,用着舒服。缺点是更新不及时,有可能无法使用 ES 的新 API
ES 官方:更新及时,灵活,缺点是太灵活了,基本是一比一复制 REST APIs,项目中使用需要二次封装。
Elasticsearch Java API Client

目前最新版本 ES8.12,要求 jdk8 以上,API 里面使用了大量的 builder 和 lambda
官方也提供了 测试用例
兼容

翻了不少博客,大部分都是使用 High Level Rest Client,这是旧版本的 api,新版本使用 Elasticsearch Java API Client,如何兼容旧版本,官方也提供了解决方案)
下文描述的均是新版 API
添加 jar 包

官方文档:(安装| Elasticsearch Java API 客户端 |松紧带 --- Installation | Elasticsearch Java API Client | Elastic)
使用的是 maven,在 pom.xml 中添加
<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>8.12.2</version>
</dependency>


<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>假如报错 ClassNotFoundException: jakarta.json.spi.JsonProvider,则还需要添加
<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
    <version>2.0.1</version>
</dependency>打印请求

在 application. yml 中添加配置,打印 es 的 http 请求(建议在开辟调试时使用)
logging:
level:
    tracer: TRACE连接 ES

配置文件如下,后续全部 ES 操作都通过 ElasticsearchClient 对象
更多配置请看 Common configuration
@Configuration
public class ElasticSearchConfig {

    @Bean
    public ElasticsearchClient esClient() {
      // ES服务器URL
      String serverUrl = "http://127.0.0.1:9200";
      // ES用户名和密码
      String userName = "xxx";
      String password = "xxx";

      BasicCredentialsProvider credsProv = new BasicCredentialsProvider();
      credsProv.setCredentials(
                AuthScope.ANY, new UsernamePasswordCredentials(userName, password)
      );

      RestClient restClient = RestClient
                .builder(HttpHost.create(serverUrl))
                .setHttpClientConfigCallback(hc -> hc.setDefaultCredentialsProvider(credsProv))
                .build();

      ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
      return new ElasticsearchClient(transport);
    }

}索引操作

代码中的 esClient 就是 ElasticsearchClient,请自行注入 bean
// 索引名字
String indexName = "student";

// 索引是否存在
BooleanResponse books = esClient.indices().exists(e -> e.index(indexName));
System.out.println("索引是否存在:" + books.value());

// 创建索引
esClient.indices().create(c -> c
      .index(indexName)
      .mappings(mappings -> mappings// 映射
                .properties("name", p -> p
                        .text(t -> t // text类型,index=false
                              .index(false)
                        )
                )
                .properties("age", p -> p
                        .long_(t -> t) // long类型
                )
      )
);

// 删除索引
esClient.indices().delete(d -> d.index(indexName));文档操作 (CRUD)

下文以官方测试数据 account. json 为例
实体类

起首定义实体类,用于 ES 中的字段
public class Account {
        private String id;
        // 解决ES中字段与实体类字段不一致的问题
        @JsonProperty("account_number")
    private Long account_number;
    private String address;
    private Integer age;
    private Long balance;
    private String city;
    private String email;
    private String employer;
    private String firstname;
    private String lastname;
    private String gender;
    private String state;
        ... 省略get、set方法
}新增

String indexName = "account";// 索引名字
Account account = new Account();
account.setId("1");
account.setLastname("guyu");   

// 新增
CreateResponse createResponse = esClient.create(c -> c
      .index(indexName) // 索引名字
      .id(account.getId()) // id
      .document(account) // 实体类
);修改

UpdateResponse<Account> updateResp = esClient.update(u -> u
                .index(indexName)
                .id(account.getId())
                .doc(account),
      Account.class
);删除

DeleteResponse deleteResp = esClient.delete(d -> d.index(indexName).id("1"));批量新增

批量操作需要使用到 bulk
List<Account> accountList = ...
BulkRequest.Builder br = new BulkRequest.Builder();
for (Account acc : accountList) {
    br.operations(op -> op
            .create(c -> c
                  .index(indexName)
                  .id(acc.getId())
                  .document(acc)
            )
    );
}
BulkResponse bulkResp = esClient.bulk(br.build());有没有觉得批量新增的 .create () 里面的参数很眼熟,批量删除和更新请举一反三
根据 id 查询

// 定义实体类
GetResponse<Account> getResp = esClient.get(g -> g.index(indexName).id("1"), Account.class);
if (getResp.found()) {
    Account source = getResp.source();// 这就是得到的实体类
    source.setId(getResp.id());
}

// 不定义实体类
GetResponse<ObjectNode> getResp = esClient.get(g -> g
                .index(indexName)
                .id("1"),
      ObjectNode.class
);
if (getResp.found()) {
    ObjectNode json = getResp.source();
    String firstname = json.get("firstname").asText();
    System.out.println(firstname);
}搜索

搜索全部
SearchResponse<Account> searchResp = esClient.search(s -> s
      .index(indexName)
      .query(q -> q.matchAll(m -> m))// 搜索全部
      , Account.class
);

HitsMetadata<Account> hits = searchResp.hits();
long totalValue = hits.total().value(); // 匹配到的数量
hits.hits().forEach(h -> {
    Account acc = h.source(); // 这就是得到的实体类
    acc.setId(h.id());
});ES API 的对象定义,基本与返回的 json 一一对应的,所以 SearchResponse 就不过多赘述。
搜索 firstname = Amber
SearchResponse<Account> searchResp = esClient.search(s -> s
      .index(indexName)
      .query(q -> q// 查询
                .match(t -> t
                        .field("firstname")
                        .query("Amber")
                )
      )
      , Account.class
);

// 也可以这样写
Query firstNameQuery = MatchQuery.of(m -> m.field("firstname").query("Amber"))._toQuery();
SearchResponse<Account> searchResp = esClient.search(s -> s
      .index(indexName)
      .query(firstNameQuery)
      , Account.class
);嵌套查询,比如搜索firstname = Amber AND age = 32
Query firstNameQuery = MatchQuery.of(m -> m.field("firstname").query("Amber"))._toQuery();
Query ageQuery = MatchQuery.of(m -> m.field("age").query(32))._toQuery();

SearchResponse<Account> searchResp = esClient.search(s -> s
      .index(indexName)
      .query(q -> q
                .bool(b -> b.must(firstNameQuery, ageQuery))
      )
      , Account.class
);浅分页

from 和 size 参数类似于 mysql 的 limit,详细阐明见 Paginate search results
SearchResponse<Account> searchResp = esClient.search(s -> s
      .index(indexName)   
      .from(0)// 分页参数
                .size(20)// 分页参数
      , Account.class
);排序

SearchResponse<Account> searchResp = esClient.search(s -> s
      .index(indexName)   
      .sort(so -> so// 排序字段1
                .field(f -> f
                        .field("age")
                        .order(SortOrder.Asc)
                )
      )
      .sort(so -> so// 排序字段2
                .field(f -> f
                        .field("account_number")
                        .order(SortOrder.Desc)
                )
      )
      , Account.class
);Spring Data Elasticsearch

文档: Spring Data Elasticsearch
添加 jar 和配置

pom.xml添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>yml 配置
spring:
elasticsearch:
    uris: http://xxx:9200
    username: xxx
    password: xxx
logging:
level:
# 输出es的查询参数(调试用)
    tracer: TRACE索引操作

实体类

@Data
@Document(indexName = "account")
public class Account {

    @Id
    private String id;
    // 解决ES中字段与实体类字段不一致的问题
    @Field(name = "account_number", type = FieldType.Long)
    private Long accountNumber;
    @Field(type = FieldType.Text)
    private String address;
    @Field(type = FieldType.Integer)
    private Integer age;
    @Field(type = FieldType.Long)
    private Long balance;
    @Field(type = FieldType.Text)
    private String city;
    @Field(type = FieldType.Text)
    private String email;
    @Field(type = FieldType.Text)
    private String employer;
    @Field(type = FieldType.Text)
    private String firstname;
    @Field(type = FieldType.Text)
    private String lastname;
    @Field(type = FieldType.Text)
    private String gender;
    @Field(type = FieldType.Text)
    private String state;
        ... 省略get、set 方法
}IndexOperations idxOpt = template.indexOps(Account.class);
// 索引是否存在
boolean idxExist = idxOpt.exists();

// 创建索引
boolean createSuccess = idxOpt.createWithMapping();
System.out.println(createSuccess);

// 删除索引
boolean deleted = idxOpt.delete();文档操作(CRUD)

Account account = new Account();
account.setId("1");
account.setLastname("guyu");

// 这是插入或覆盖,如果id存在了就是覆盖
template.save(account);

// 修改,用的是es的_update
template.update(account);

// 删除
template.delete(account)

// 批量新增(用的是es的_bulk)
List<Account> accountList = ...
template.save(accountList);

// 根据id查询
Account account = template.get("1", Account.class);搜索 + 排序 + 分页

// 搜索 firstname = Amber AND age = 32
Criteria criteria = new Criteria();
criteria.and(new Criteria("firstname").is("Amber"));
criteria.and(new Criteria("age").is(32));

// 分页
int pageNum = 1; // 页码
int pageSize = 20; // 每页数量
Query query = new CriteriaQueryBuilder(criteria)
      .withSort(Sort.by(new Order(Sort.Direction.ASC, "age"))) // 排序字段1
      .withSort(Sort.by(new Order(Sort.Direction.DESC, "balance"))) // 排序字段1
      .withPageable(PageRequest.of(pageNum - 1, pageSize)) // 浅分页
      // 不需要查询的字段
      .withSourceFilter(new FetchSourceFilterBuilder().withExcludes("email", "address").build())
      .build();

SearchHits<Account> searchHits = template.search(query, Account.class);
long totalValue = searchHits.getTotalHits(); // 匹配到的数量
for (SearchHit<Account> searchHit : searchHits.getSearchHits()) {
    Account account = searchHit.getContent(); // 这就是得到的实体类
}总结

本文先容了 SpringBoot 整合 ElasticSearch 的两种方案,但均只是简单提及,更详细的用法需要自行查看官方文档。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: ElasticSearch8 - SpringBoot整合ElasticSearch