Skip to content

Java Client介绍

本文介绍如何在Java中操作Elasticsearch,主要介绍官方提供的Java Client的使用方法。

注意,Java High Level REST Client在7.15.0版本被废弃了,所以本文介绍最新的Elasticsearch Java client。

1. 引入依赖

引入与ES版本相同的客户端:

xml
<dependency>
    <groupId>co.elastic.clients</groupId>
    <artifactId>elasticsearch-java</artifactId>
    <version>9.2.0</version>
</dependency>
<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
    <version>2.1.3</version>
</dependency>

注意,如果出现ClassNotFoundException: jakarta.json.spi.JsonProvider,需要引入jakarta.json-api依赖,具体参考:https://www.elastic.co/docs/reference/elasticsearch/clients/java/setup/installation

2. 创建连接

参考连接:https://www.elastic.co/docs/reference/elasticsearch/clients/java/setup/connecting

参考代码:

java
public class EsUtil {
    // 指定ES的地址
    private static final String SERVER_URL = "https://localhost:9201";
    // 指定连接到ES的用户名
    private static final String USERNAME = "xxx";
    // 指定连接到ES的密码
    private static final String PASSWORD = "yyy";
    // 指定根CA证书路径
    private static final String CERT_FILE_PATH = "D:\\software\\elasticsearch\\elasticsearch-9.2.0\\config\\certs\\http_ca.crt";

    private static final File certFile;
    private static final SSLContext sslContext;
    private static final ElasticsearchClient esClient;

    static {
        // 创建ES客户端
        try {
            certFile = new File(CERT_FILE_PATH);
            sslContext = TransportUtils.sslContextFromHttpCaCrt(certFile);
            esClient = ElasticsearchClient.of(b -> b
                    .host(SERVER_URL)
                    .usernameAndPassword(USERNAME, PASSWORD)
                    .sslContext(sslContext)
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static ElasticsearchClient getEsClient() {
        return esClient;
    }

}

除了使用CA证书,也可以使用证书指纹。

3. 索引操作

3.1 创建索引

java
@Test
void testCreateIndex() throws IOException {
    // 获取ES客户端
    ElasticsearchClient esClient = EsUtil.getEsClient();

    // 索引名称
    String indexName = "user";

    // 设置Mappings
    Map<String, Property> properties = new HashMap<>();
    properties.put("id", Property.of(x->x.long_(p -> p)));
    properties.put("name", Property.of(x->x.keyword(p -> p)));
    properties.put("address", Property.of(x->x.text(p -> p.analyzer("standard")
            .fields("keyword", f -> f.keyword(p1 -> p1))))
    );
    properties.put("age", Property.of(x->x.integer(p -> p)));

    // 设置索引别名:该别名具有视图功能,只查询年龄大于10的用户
    Alias alias = Alias.of(a -> a.filter(q -> q.range(r -> r.number(n -> n.field("age").gt(10.0)))));

    // 创建索引
    CreateIndexResponse createIndexResponse = esClient.indices().create(b ->
            // 索引名
            b.index(indexName)
                    // 索引mappings
                    .mappings(t -> t.properties(properties))
                    // 索引设置
                    .settings(s -> s.numberOfShards("2").numberOfReplicas("2"))
                    // 索引别名
                    .aliases("user_age_over_10", alias)
    );

    System.out.println(createIndexResponse);

}
txt
CreateIndexResponse: {"index":"user","shards_acknowledged":true,"acknowledged":true}

3.2 判断索引是否存在

java
@Test
void testIndexExists() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();

    String indexName = "user";
    BooleanResponse booleanResponse = esClient.indices().exists(b -> b.index(indexName));
    System.out.println(booleanResponse.value());
}

3.3 删除索引

java
@Test
void testDeleteIndex() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();

    String indexName = "user";
    DeleteIndexResponse deleteIndexResponse = esClient.indices().delete(b -> b.index(indexName));
    System.out.println(deleteIndexResponse);
}
txt
DeleteIndexResponse: {"acknowledged":true}

4. 文档操作

4.1 索引单个文档

在ES Java客户端中,可以直接索引对象,如下:

java
@Test
void testIndexDocument() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();

    // 准备要插入的数据(对象)
    User user = new User(1L, "张三", 18, "上海");

    // 指定索引名
    String indexName = "user";
    IndexResponse indexResponse = esClient.index(i ->
            i.index(indexName)
                    // 指定文档ID,这里使用数据库ID;如果不指定,则由ES自动生成
                    .id(user.getId().toString())
                    .document(user)
    );
    System.out.println(indexResponse);
}
txt
IndexResponse: {"_id":"1","_index":"user","_primary_term":1,"result":"created","_seq_no":0,"_shards":{"failed":0.0,"successful":1.0,"total":2.0},"_version":1}

![image-20251119155714038](./assets/Java Client/image-20251119155714038.png)

TIP

补充,在ES中关于index与create的区别:

Index (索引/覆写) = “存在即覆盖,不存在则新增” ,在Java中使用client.index()方法;

Create (创建/唯写) = “必须不存在才能新增,已存在则报错” ,在Java中使用client.create()方法

4.2 根据ID获取文档

java
@Test
void testGetDocById() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();

    String indexName = "user";
    GetResponse<User> response = esClient.get(g -> g
                    .index(indexName)
                    .id("1"),
            User.class
    );

    if (response.found()) {
        User user = response.source();
        System.out.println("user : " + user);
    } else {
        System.out.println("user not found");
    }
}

4.3 根据ID删除文档

java
@Test
void testDeleteDocById() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();

    String indexName = "user";
    DeleteResponse deleteResponse = esClient.delete(d ->
            d.index(indexName).id("1")
    );
    System.out.println(deleteResponse);
}

4.4 根据ID更新文档

更新文档分为全量更新和局部更新。

全量更新:

java
@Test
void testUpdateDocById() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();
    String indexName = "user";
    UpdateResponse<User> updateResponse = esClient.update(u ->
            u.index(indexName).id("2")
                    .doc(new User(2L, "李四", 33, "US")),
            User.class
    );
    System.out.println(updateResponse);
}

局部更新:

方法一:使用对象,通过将不更新的字段设置为null,进行局部更新;

java
@Test
void testPartialUpdateDocById() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();
    String indexName = "user";
    UpdateResponse<User> updateResponse = esClient.update(u ->
                    u.index(indexName).id("2")
                            .doc(new User(2L, null, 22, null)),
            User.class
    );
    System.out.println(updateResponse);
}

方法二:使用Map,只传入要更新的字段;

java
@Test
void testPartialUpdateDocById2() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();
    String indexName = "user";

    Map<String, Object> updates = new HashMap<>();
    updates.put("name", "王五");

    UpdateResponse<User> updateResponse = esClient.update(u ->
                    u.index(indexName).id("2")
                            .doc(updates),
            User.class
    );
    System.out.println(updateResponse);
}

4.5 upsert

upsert 是一种鲁棒性很强的操作:如果 ID 对应的文档存在,就执行局部更新 (doc);如果不存在,就插入一条全新的默认数据 (upsert)。

java
@Test
void testUpsert() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();
    String indexName = "user";
    User user = new User(3L, "赵六", 9, "北京");
    
    UpdateResponse<User> updateResponse = esClient.update(u ->
                    u.index(indexName).id("3")
                            .doc(user)
                            .upsert(user),
            User.class
    );
    System.out.println(updateResponse);
}

可以将局部更新与Upsert结合:

java
@Test
void testUpsert() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();
    String indexName = "user";
    User user = new User(3L, "赵六", 9, "北京");

    Map<String, Object> updates = new HashMap<>();
    updates.put("name", "赵六new");

    UpdateResponse<User> updateResponse = esClient.update(u ->
                    u.index(indexName).id("4")
                            .doc(updates)
                            .upsert(user),
            User.class
    );
    System.out.println(updateResponse);
}

5. 搜索

在Java客户端可以使用search()方法执行搜索_search API 。

例如,简单执行一个term搜索:

java
@Test
void testSearch() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();

    String indexName = "user";

    SearchResponse<User> searchResponse = esClient.search(s ->
                    s.index(indexName)
                            .query(q -> q.term(t -> t.field("name").value("王五"))),
            User.class
    );

    TotalHits total = searchResponse.hits().total();
    boolean isExactResult = total.relation() == TotalHitsRelation.Eq;

    if (isExactResult) {
        System.out.println("There are " + total.value() + " results");
    } else {
        System.out.println("There are at least " + total.value() + " results");
    }

    List<Hit<User>> hits = searchResponse.hits().hits();
    for (Hit<User> hit: hits) {
        User user = hit.source();
        System.out.println(user);
    }

}

如果有多个条件,可以使用bool查询,如下:

java
@Test
void testSearch2() throws IOException {
    ElasticsearchClient esClient = EsUtil.getEsClient();

    String indexName = "user";

    Query nameQuery = TermQuery.of(q -> q.field("name").value("王五"))._toQuery();
    Query ageQuery = RangeQuery.of(q -> q.number(n -> n.field("age").gt(10.0)))._toQuery();

    SearchResponse<User> searchResponse = esClient.search(s ->
                    s.index(indexName)
                            .query(q ->
                                    q.bool(b ->
                                            b.filter(ageQuery).mustNot(nameQuery))
                            ),
            User.class
    );

    TotalHits total = searchResponse.hits().total();
    boolean isExactResult = total.relation() == TotalHitsRelation.Eq;

    if (isExactResult) {
        System.out.println("There are " + total.value() + " results");
    } else {
        System.out.println("There are at least " + total.value() + " results");
    }

    List<Hit<User>> hits = searchResponse.hits().hits();
    for (Hit<User> hit: hits) {
        User user = hit.source();
        System.out.println(user);
    }
}

Easy-Es的使用

Easy-Es(简称EE)是一款基于ElasticSearch(简称Es)官方提供的ElasticsearchClient打造的ORM开发框架,在 ElasticsearchClient 的基础上,只做增强不做改变,为简化开发、提高效率而生,您如果有用过Mybatis-Plus(简称MP),那么您基本可以零学习成本直接上手EE,EE是MP的Es平替版,在有些方面甚至比MP更简单,同时也融入了更多Es独有的功能,助力您快速实现各种场景的开发.

官网地址:https://www.easy-es.cn

这里只介绍基础入门以及搜索写法。

1. 入门案例

基于Spring Boot3,ES版本为7.17.28

首先引入配置:

xml
<dependency>
    <groupId>org.dromara.easy-es</groupId>
    <artifactId>easy-es-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

然后在配置文件配置ES地址、密码:

yaml
easy-es:
  compatible: true # 兼容模式开关,默认为false,若您的es客户端版本小于8.x,务必设置为true才可正常使用,8.x及以上则可忽略此项配置
  enable: true # 默认为true,若为false时,则认为不启用本框架
  address: 127.0.0.1:9201  #填你的es连接地址
  # username: 有设置才填写,非必须
  # password: 有设置才填写,非必须

在程序主类上添加Mapper扫描路径:

java
@SpringBootApplication
@MapperScan("org.example.demo.mapper.mp")
@EsMapperScan("org.example.demo.mapper.es")
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

接下来设置实体类和数据访问层:

java
@Data
@IndexName("student")
public class Student {
    @IndexId(type = IdType.NONE)
    private String id;

    @IndexField(value = "name", fieldType = FieldType.KEYWORD)
    private String name;

    @IndexField(value = "grade", fieldType = FieldType.KEYWORD)
    private String grade;

    @IndexField(value = "class", fieldType = FieldType.KEYWORD)
    private String className;

    @IndexField(value = "chinese_score", fieldType = FieldType.INTEGER)
    private Integer chineseScore;

  	@IndexField(value = "address", fieldType = FieldType.TEXT)
    private String address;
}
java
import org.apache.ibatis.annotations.Mapper;
import org.dromara.easyes.core.kernel.BaseEsMapper;
import org.example.demo.model.es.Student;

@Mapper
public interface EsStudentMapper extends BaseEsMapper<Student> {
}

测试代码:

java
@SpringBootTest
class MysqlTimeDemoApplicationTests {
    @Autowired
    private EsStudentMapper esStudentMapper;

    @Test
    void esTest() {
        Student student = esStudentMapper.selectById(1);
        System.out.println(student);
    }

}

结果如下:

txt
2025-11-22T11:08:11.262+08:00  INFO 11478 --- [sss-aa] [           main] easy-es                                  : ===> Execute By Easy-Es: 
POST /student/_search?typed_keys=true
{"query":{"ids":{"values":["1"]}}}
2025-11-22T11:08:11.322+08:00  WARN 11478 --- [sss-aa] [           main] org.elasticsearch.client.RestClient      : request [POST http://127.0.0.1:9201/student/_search?typed_keys=true] returned 1 warnings: [299 Elasticsearch-7.17.28-139cb5a961d8de68b8e02c45cc47f5289a3623af "Elasticsearch built-in security features are not enabled. Without authentication, your cluster could be accessible to anyone. See https://www.elastic.co/guide/en/elasticsearch/reference/7.17/security-minimal-setup.html to enable security."]
Student(id=1, name=张三, grade=高一, className=一班, chineseScore=98, address=广州市海珠区)

可以发现正常取回结果,并且也有相关日志打印。

2. 搜索

本小节介绍如何使用Easy-Es进行文档搜索。

官网介绍:https://www.easy-es.cn/pages/17ea0a/#es四大嵌套查询

案例:

java
@Test
void testComplesSearch(){
    SearchResponse<Student> searchResponse = esStudentMapper.search(
            EsWrappers.lambdaQuery(Student.class)
                    .filter(x -> x.eq(Student::getGrade, "高一")
                            .eq(Student::getClassName, "二班"))
                    .not(x -> x.lt(Student::getChineseScore, 60))
                    .should(x -> x.match(Student::getAddress, "荔湾"))
    );

    TotalHits totalHits = searchResponse.hits().total();
    if(totalHits.relation() == TotalHitsRelation.Eq){
        System.out.println("总记录数:" + totalHits.value());
    }

    for (Hit<Student> hit : searchResponse.hits().hits()) {
        Student student = hit.source();
        System.out.println("得分:" + hit.score() + "  " + student);
    }
}

生成的DSL语句如下:

json
{"query":{"bool":{"filter":[{"bool":{"must":[{"term":{"grade":{"boost":1.0,"value":"高一"}}},{"term":{"class":{"boost":1.0,"value":"二班"}}}]}}],"must_not":[{"bool":{"must":[{"range":{"chinese_score":{"boost":1.0,"lt":60}}}]}}],"should":[{"bool":{"must":[{"match":{"address":{"boost":1.0,"query":"荔湾"}}}]}}]}},"size":10000,"track_total_hits":true}

结果如下:

txt
总记录数:3
得分:1.9616582  Student(id=null, name=赵六, grade=高一, className=二班, chineseScore=92, address=广州市荔湾区)
得分:0.5753642  Student(id=null, name=钱七, grade=高一, className=二班, chineseScore=89, address=广州市荔湾区)
得分:0.0  Student(id=null, name=王五, grade=高一, className=二班, chineseScore=90, address=广州市天河区)

3. ES客户端

Easy-Es并不能完全覆盖ElasticSearch客户端的功能,所以,我们此时仍然可以使用ElasticSearch客户端:

java
@Autowired
private ElasticsearchClient elasticsearchClient;

@Test
void testEsClient() throws IOException {
    GetResponse<Student> student = elasticsearchClient.get(
            GetRequest.of(x -> x.index("student").id("1")),
            Student.class
    );
    System.out.println(student);
}