Appearance
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}
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独有的功能,助力您快速实现各种场景的开发.
这里只介绍基础入门以及搜索写法。
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);
}