Elasticsearch 学习笔记

基于 Elasticsearch 8.19.0 官方 Java API 客户端,涵盖依赖配置、客户端初始化、CRUD 操作及查询示例。


一、技术选型与版本

组件 版本 说明
Elasticsearch Server 8.x 目标 ES 服务端版本
elasticsearch-java 8.19.0 官方 Java API 客户端(类型安全、Fluent DSL)
elasticsearch-rest-client 8.19.0 底层 HTTP 传输层
jakarta.json-api 2.1.3 JSON-P API 规范
parsson 1.1.7 JSON-P 实现(运行时)

为什么不用 Spring Data Elasticsearch?

直接使用 ES 官方 co.elastic.clients Java API 客户端,而非 spring-boot-starter-data-elasticsearch,原因:

  • 更灵活,不依赖 Spring Data 的 @Document@Field 注解体系
  • 使用官方最新的 Fluent DSL 风格,代码可读性高
  • 避免 Spring Data 封装层的版本滞后问题

二、Maven 依赖

<!-- Elasticsearch Java Client(官方高级API) -->
<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>8.19.0</version>
</dependency>

<!-- ES底层REST客户端 -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>8.19.0</version>
</dependency>

<!-- JSON-P API(JSON处理规范) -->
<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
    <version>2.1.3</version>
</dependency>

<!-- JSON-P 实现(运行时) -->
<dependency>
    <groupId>org.eclipse.parsson</groupId>
    <artifactId>parsson</artifactId>
    <version>1.1.7</version>
</dependency>

依赖关系说明:

elasticsearch-java (高级API,Fluent DSL)
    └── elasticsearch-rest-client (底层HTTP通信)
            └── apache httpclient (HTTP连接管理、认证)
    └── jakarta.json-api (JSON序列化/反序列化规范)
            └── parsson (JSON-P的具体实现)

三、ES 连接配置

3.1 YAML 配置

es:
  host: localhost
  port: 9200
  username: elastic
  password: 123456

注意: 如果使用 Spring Boot 多环境配置(dev / test / prod),每个环境的 yaml 文件都需要包含对应的 ES 配置,否则启动时会因 @Value 注入失败而报错。

3.2 Java 配置类

@Configuration
public class ElasticsearchConfig {

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

    @Value("${es.port}")
    private Integer port;

    @Value("${es.username}")
    private String userName;

    @Value("${es.password}")
    private String passWord;

    @Bean
    public ElasticsearchClient elasticsearchClient() {
        // 1. 构建认证凭证
        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(
            AuthScope.ANY,
            new UsernamePasswordCredentials(userName, passWord)
        );

        // 2. 构建底层 REST 客户端
        RestClient restClient = RestClient.builder(new HttpHost(host, port))
            .setHttpClientConfigCallback(httpClient ->
                httpClient.setDefaultCredentialsProvider(credentialsProvider)
            )
            .build();

        // 3. 包装为 Transport 层(指定 JSON 映射器)
        RestClientTransport transport = new RestClientTransport(
            restClient,
            new JacksonJsonpMapper()
        );

        // 4. 创建高级客户端
        return new ElasticsearchClient(transport);
    }
}

组件层级结构:

ElasticsearchClient          ← 业务代码直接使用,提供 Fluent DSL
    └── RestClientTransport  ← 传输抽象层,负责请求/响应序列化
            ├── RestClient           ← 底层 HTTP 客户端(连接池、认证)
            └── JacksonJsonpMapper   ← JSON ↔ Java 对象的映射器

关键点:

  • BasicCredentialsProvider + AuthScope.ANY 实现全局 HTTP Basic 认证
  • JacksonJsonpMapper 利用 Jackson 生态进行 JSON 处理
  • ElasticsearchClient 作为 Spring Bean 注入,线程安全,全局复用

四、文档模型

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserPortrait {

    private String name;      // 用户名
    private Integer age;      // 年龄
    private String portrait;  // 用户画像描述(全文搜索字段)
}

说明:

  • 纯 POJO,不使用 @Document@Field@Id 等注解
  • JSON 序列化/反序列化由 JacksonJsonpMapper 自动完成
  • 索引名称和文档 ID 在代码中显式指定,而非通过注解声明
  • 文档类必须有无参构造器 + getter/setter,或使用 Lombok @Data + @NoArgsConstructor

五、核心 API 操作

5.1 新增文档

CreateResponse createResponse = elasticsearchClient.create(
    client -> client
        .index("user_portrait_index")   // 指定索引名
        .id("9")                         // 文档ID
        .document(userPortrait)          // 文档对象
);

对应 ES REST API: PUT /{index}/_create/{id}

注意事项:

  • create() 对应 ES 的 _create 端点,若文档已存在则报错
  • 如需 upsert 语义(存在则更新),应使用 index()

请求示例:

{
    "name": "张三",
    "age": 25,
    "portrait": "Java开发工程师,熟悉微服务架构"
}

5.2 按 ID 查询

GetResponse<UserPortrait> response = elasticsearchClient.get(
    client -> client
        .index("user_portrait_index")
        .id(id),
    UserPortrait.class    // 指定反序列化目标类型
);
UserPortrait result = response.source();

对应 ES REST API: GET /{index}/_doc/{id}

注意事项:

  • 若文档不存在,response.source() 返回 null
  • 第二个参数 .class 决定了 _source 反序列化的目标类型

5.3 全文搜索

SearchResponse<UserPortrait> response = elasticsearchClient.search(
    client -> client
        .size(10)   // 返回最多10条
        .query(q -> q.bool(b -> b
            // must子句:全文匹配 portrait 字段
            .must(m -> m.match(mm -> mm
                .field("portrait")
                .query(keyword)
            ))
            // filter子句:年龄 >= 10
            .filter(f -> f.range(r -> r
                .number(n -> n
                    .field("age")
                    .gte(10.0)
                )
            ))
        )),
    UserPortrait.class
);

List<UserPortrait> results = response.hits().hits().stream()
    .map(Hit::source)
    .collect(Collectors.toList());

对应 ES REST API: POST /{index}/_search

等效的 ES REST 请求体:

{
    "size": 10,
    "query": {
        "bool": {
            "must": [
                { "match": { "portrait": "Java微服务" } }
            ],
            "filter": [
                { "range": { "age": { "gte": 10 } } }
            ]
        }
    }
}

查询解析:

  • match 查询对 portrait 字段做全文分词检索,参与相关性评分
  • range 过滤 age >= 10 的文档,不参与评分,可被 ES 缓存,性能更好
  • size(10) 限制返回最多 10 条结果

六、Fluent DSL 核心模式

ES Java 8.x 客户端使用 Lambda 表达式构建请求,核心模式如下:

elasticsearchClient.<操作类型>(client -> client
    .<参数1>(p1 -> p1
        .<子参数>(p2 -> p2
            .<具体设置>()
        )
    ),
    <返回类型>.class
);

常用操作速查

操作 方法 ES REST API 用途
创建文档 elasticsearchClient.create() PUT /{index}/_create/{id} 新建文档(已存在则报错)
索引文档 elasticsearchClient.index() PUT /{index}/_doc/{id} 新建或替换文档(upsert)
获取文档 elasticsearchClient.get() GET /{index}/_doc/{id} 按 ID 获取
搜索 elasticsearchClient.search() POST /{index}/_search 全文搜索、聚合查询
更新文档 elasticsearchClient.update() POST /{index}/_update/{id} 部分更新
删除文档 elasticsearchClient.delete() DELETE /{index}/_doc/{id} 按 ID 删除

Bool 查询组合

q.bool(b -> b
    .must(...)       // 必须匹配,参与评分
    .filter(...)     // 必须匹配,不参与评分(缓存友好)
    .should(...)     // 可选匹配,提高相关性
    .mustNot(...)    // 必须不匹配
)

must vs filter 的区别:

  • must:参与相关性评分计算,影响 _score 排序
  • filter:只做过滤(是/否),不计算评分,可利用缓存,性能更好

七、ES 连接参数说明

参数 YAML 路径 说明
主机地址 es.host ES 服务地址,如 localhost
端口 es.port ES HTTP 端口,默认 9200
用户名 es.username ES 认证用户名
密码 es.password ES 认证密码

八、常见问题

8.1 启动报错 Could not resolve placeholder 'es.host'

某个 Spring profile 的 yaml 文件中缺少 ES 配置,需在对应的 yaml 文件中补全。

8.2 文档 ID 策略

  • 使用业务主键(如用户 ID)作为文档 ID,便于按 ID 更新
  • 调用 elasticsearchClient.index() 不指定 ID,让 ES 自动生成 UUID

8.3 create() vs index() 的区别

方法 文档已存在时 文档不存在时
create() 报错(409 Conflict) 创建新文档
index() 替换现有文档 创建新文档

8.4 结果集解析

SearchResponse<T> response = ...;

// 命中的文档列表
List<Hit<T>> hits = response.hits().hits();

// 提取 _source 对象
List<T> sources = hits.stream().map(Hit::source).collect(Collectors.toList());

// 命中总数(注意 ES 7.x+ 默认最多统计 10000)
long total = response.hits().total().value();

// 最高相关性评分
double maxScore = response.hits().maxScore();

九、扩展阅读

基于 Elasticsearch 8.19.0 官方 Java API 客户端,涵盖依赖配置、客户端初始化、CRUD 操作及查询示例。


一、技术选型与版本

组件 版本 说明
Elasticsearch Server 8.x 目标 ES 服务端版本
elasticsearch-java 8.19.0 官方 Java API 客户端(类型安全、Fluent DSL)
elasticsearch-rest-client 8.19.0 底层 HTTP 传输层
jakarta.json-api 2.1.3 JSON-P API 规范
parsson 1.1.7 JSON-P 实现(运行时)

为什么不用 Spring Data Elasticsearch?

直接使用 ES 官方 co.elastic.clients Java API 客户端,而非 spring-boot-starter-data-elasticsearch,原因:

  • 更灵活,不依赖 Spring Data 的 @Document@Field 注解体系
  • 使用官方最新的 Fluent DSL 风格,代码可读性高
  • 避免 Spring Data 封装层的版本滞后问题

二、Maven 依赖

<!-- Elasticsearch Java Client(官方高级API) -->
<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>8.19.0</version>
</dependency>

<!-- ES底层REST客户端 -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>8.19.0</version>
</dependency>

<!-- JSON-P API(JSON处理规范) -->
<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
    <version>2.1.3</version>
</dependency>

<!-- JSON-P 实现(运行时) -->
<dependency>
    <groupId>org.eclipse.parsson</groupId>
    <artifactId>parsson</artifactId>
    <version>1.1.7</version>
</dependency>

依赖关系说明:

elasticsearch-java (高级API,Fluent DSL)
    └── elasticsearch-rest-client (底层HTTP通信)
            └── apache httpclient (HTTP连接管理、认证)
    └── jakarta.json-api (JSON序列化/反序列化规范)
            └── parsson (JSON-P的具体实现)

三、ES 连接配置

3.1 YAML 配置

es:
  host: localhost
  port: 9200
  username: elastic
  password: 123456

注意: 如果使用 Spring Boot 多环境配置(dev / test / prod),每个环境的 yaml 文件都需要包含对应的 ES 配置,否则启动时会因 @Value 注入失败而报错。

3.2 Java 配置类

@Configuration
public class ElasticsearchConfig {

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

    @Value("${es.port}")
    private Integer port;

    @Value("${es.username}")
    private String userName;

    @Value("${es.password}")
    private String passWord;

    @Bean
    public ElasticsearchClient elasticsearchClient() {
        // 1. 构建认证凭证
        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(
            AuthScope.ANY,
            new UsernamePasswordCredentials(userName, passWord)
        );

        // 2. 构建底层 REST 客户端
        RestClient restClient = RestClient.builder(new HttpHost(host, port))
            .setHttpClientConfigCallback(httpClient ->
                httpClient.setDefaultCredentialsProvider(credentialsProvider)
            )
            .build();

        // 3. 包装为 Transport 层(指定 JSON 映射器)
        RestClientTransport transport = new RestClientTransport(
            restClient,
            new JacksonJsonpMapper()
        );

        // 4. 创建高级客户端
        return new ElasticsearchClient(transport);
    }
}

组件层级结构:

ElasticsearchClient          ← 业务代码直接使用,提供 Fluent DSL
    └── RestClientTransport  ← 传输抽象层,负责请求/响应序列化
            ├── RestClient           ← 底层 HTTP 客户端(连接池、认证)
            └── JacksonJsonpMapper   ← JSON ↔ Java 对象的映射器

关键点:

  • BasicCredentialsProvider + AuthScope.ANY 实现全局 HTTP Basic 认证
  • JacksonJsonpMapper 利用 Jackson 生态进行 JSON 处理
  • ElasticsearchClient 作为 Spring Bean 注入,线程安全,全局复用

四、文档模型

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserPortrait {

    private String name;      // 用户名
    private Integer age;      // 年龄
    private String portrait;  // 用户画像描述(全文搜索字段)
}

说明:

  • 纯 POJO,不使用 @Document@Field@Id 等注解
  • JSON 序列化/反序列化由 JacksonJsonpMapper 自动完成
  • 索引名称和文档 ID 在代码中显式指定,而非通过注解声明
  • 文档类必须有无参构造器 + getter/setter,或使用 Lombok @Data + @NoArgsConstructor

五、核心 API 操作

5.1 新增文档

CreateResponse createResponse = elasticsearchClient.create(
    client -> client
        .index("user_portrait_index")   // 指定索引名
        .id("9")                         // 文档ID
        .document(userPortrait)          // 文档对象
);

对应 ES REST API: PUT /{index}/_create/{id}

注意事项:

  • create() 对应 ES 的 _create 端点,若文档已存在则报错
  • 如需 upsert 语义(存在则更新),应使用 index()

请求示例:

{
    "name": "张三",
    "age": 25,
    "portrait": "Java开发工程师,熟悉微服务架构"
}

5.2 按 ID 查询

GetResponse<UserPortrait> response = elasticsearchClient.get(
    client -> client
        .index("user_portrait_index")
        .id(id),
    UserPortrait.class    // 指定反序列化目标类型
);
UserPortrait result = response.source();

对应 ES REST API: GET /{index}/_doc/{id}

注意事项:

  • 若文档不存在,response.source() 返回 null
  • 第二个参数 .class 决定了 _source 反序列化的目标类型

5.3 全文搜索

SearchResponse<UserPortrait> response = elasticsearchClient.search(
    client -> client
        .size(10)   // 返回最多10条
        .query(q -> q.bool(b -> b
            // must子句:全文匹配 portrait 字段
            .must(m -> m.match(mm -> mm
                .field("portrait")
                .query(keyword)
            ))
            // filter子句:年龄 >= 10
            .filter(f -> f.range(r -> r
                .number(n -> n
                    .field("age")
                    .gte(10.0)
                )
            ))
        )),
    UserPortrait.class
);

List<UserPortrait> results = response.hits().hits().stream()
    .map(Hit::source)
    .collect(Collectors.toList());

对应 ES REST API: POST /{index}/_search

等效的 ES REST 请求体:

{
    "size": 10,
    "query": {
        "bool": {
            "must": [
                { "match": { "portrait": "Java微服务" } }
            ],
            "filter": [
                { "range": { "age": { "gte": 10 } } }
            ]
        }
    }
}

查询解析:

  • match 查询对 portrait 字段做全文分词检索,参与相关性评分
  • range 过滤 age >= 10 的文档,不参与评分,可被 ES 缓存,性能更好
  • size(10) 限制返回最多 10 条结果

六、Fluent DSL 核心模式

ES Java 8.x 客户端使用 Lambda 表达式构建请求,核心模式如下:

elasticsearchClient.<操作类型>(client -> client
    .<参数1>(p1 -> p1
        .<子参数>(p2 -> p2
            .<具体设置>()
        )
    ),
    <返回类型>.class
);

常用操作速查

操作 方法 ES REST API 用途
创建文档 elasticsearchClient.create() PUT /{index}/_create/{id} 新建文档(已存在则报错)
索引文档 elasticsearchClient.index() PUT /{index}/_doc/{id} 新建或替换文档(upsert)
获取文档 elasticsearchClient.get() GET /{index}/_doc/{id} 按 ID 获取
搜索 elasticsearchClient.search() POST /{index}/_search 全文搜索、聚合查询
更新文档 elasticsearchClient.update() POST /{index}/_update/{id} 部分更新
删除文档 elasticsearchClient.delete() DELETE /{index}/_doc/{id} 按 ID 删除

Bool 查询组合

q.bool(b -> b
    .must(...)       // 必须匹配,参与评分
    .filter(...)     // 必须匹配,不参与评分(缓存友好)
    .should(...)     // 可选匹配,提高相关性
    .mustNot(...)    // 必须不匹配
)

must vs filter 的区别:

  • must:参与相关性评分计算,影响 _score 排序
  • filter:只做过滤(是/否),不计算评分,可利用缓存,性能更好

七、ES 连接参数说明

参数 YAML 路径 说明
主机地址 es.host ES 服务地址,如 localhost
端口 es.port ES HTTP 端口,默认 9200
用户名 es.username ES 认证用户名
密码 es.password ES 认证密码

八、常见问题

8.1 启动报错 Could not resolve placeholder 'es.host'

某个 Spring profile 的 yaml 文件中缺少 ES 配置,需在对应的 yaml 文件中补全。

8.2 文档 ID 策略

  • 使用业务主键(如用户 ID)作为文档 ID,便于按 ID 更新
  • 调用 elasticsearchClient.index() 不指定 ID,让 ES 自动生成 UUID

8.3 create() vs index() 的区别

方法 文档已存在时 文档不存在时
create() 报错(409 Conflict) 创建新文档
index() 替换现有文档 创建新文档

8.4 结果集解析

SearchResponse<T> response = ...;

// 命中的文档列表
List<Hit<T>> hits = response.hits().hits();

// 提取 _source 对象
List<T> sources = hits.stream().map(Hit::source).collect(Collectors.toList());

// 命中总数(注意 ES 7.x+ 默认最多统计 10000)
long total = response.hits().total().value();

// 最高相关性评分
double maxScore = response.hits().maxScore();

九、扩展阅读

更多推荐