前邊我們學(xué)習(xí)了如何使用 RESTful API 去操作 ES,這種方式可能在實(shí)際項(xiàng)目中用的比較少,但這些內(nèi)容都是必備的基礎(chǔ),對(duì)后續(xù)的學(xué)習(xí)還是很有幫助的,還是需要掌握的。
Elasticsearch Clients 提供了許多語言的支持,我們要學(xué)習(xí)其中的 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、添加依賴
這里我們創(chuàng)建一個(gè) Spring Boot 項(xiàng)目,添加如下依賴來引入 Java High Level REST Client:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
由于 ES 的版本更新比較快,基本每個(gè)月會(huì)更新1-2個(gè)版本,但 Spring Data Elasticsearch 對(duì) ES 新版本的支持還是比較滯后的,如果 Spring Data Elasticsearch 對(duì)應(yīng)的 ES 版本比你安裝的 ES 版本低,建議直接修改 Spring Data Elasticsearch 對(duì)應(yīng)的 ES 版本,使其和你安裝的 ES 版本保持一致。
我創(chuàng)建的基于 Spring Boot 2.3.6.RELEASE 的項(xiàng)目,其中的 ES 版本為7.6.2:

我之前安裝的是 ES7.9.3 版本,所以可通過如下方式修改 Spring Data Elasticsearch 對(duì)應(yīng)的 ES 版本號(hào)(目前最新的 Spring Boot 2.4.0 已經(jīng)支持 ES7.9.3 了):
<properties>
<elasticsearch.version>7.9.3</elasticsearch.version>
</properties>
通過 Spring Data Elasticsearch 來集成 Java High Level REST Client,就可以使用 Spring Data 的一些特性來簡化開發(fā),但前期我們先學(xué)習(xí) Java High Level REST Client 的原生 API,這個(gè)更具有通用性,掌握了原生 API 再學(xué)習(xí)整合 Spring Data 后的一些特性就很容易了。
由于還會(huì)用到 JSON 相關(guān)的操作,這里添加fastjson依賴:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
2、初始化
創(chuàng)建如下配置類,來連接到 ES 節(jié)點(diǎn),創(chuàng)建RestHighLevelClient對(duì)象,這樣初始化工作就完成了。RestHighLevelClient是重點(diǎn),后續(xù)的各種操作都要使用它:
@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;
}
}
我們先創(chuàng)建一個(gè)UserService類,里邊注入通過配置類生成的RestHighLevelClient對(duì)象,所有的操作的方法都在該類里完成:
@Service
public class UserService {
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
}
3、創(chuàng)建索引
如下的代碼會(huì)創(chuàng)建一個(gè)名為user的索引:
public void createIndex() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("user");
CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
System.out.println(response.isAcknowledged());
}
創(chuàng)建索引時(shí)也可以根據(jù)需要指定一些配置信息,例如分片數(shù)量、文檔字段的映射信息、索引別名等:
public void createIndex2() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("user");
// 索引分片數(shù)量配置
request.settings(Settings.builder()
.put("index.number_of_shards", 2)
.put("index.number_of_replicas", 1));
// 設(shè)置文檔字段的映射信息
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設(shè)置文檔字段的映射信息
// request.mapping("{\n" +
// " \"properties\": {\n" +
// " \"birthday\": {\n" +
// " \"type\": \"date\",\n" +
// " \"format\": \"yyyy-MM-dd\"\n" +
// " }\n" +
// " }\n" +
// "}", XContentType.JSON);
// 設(shè)置索引別名
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、添加文檔
創(chuàng)建好了索引就可以給里邊添加文檔數(shù)據(jù)了,首先看添加單個(gè)文檔:
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));
// 超時(shí)時(shí)間
request.timeout("2s");
// 文檔id
request.id("1");
// 設(shè)置要添加的數(shù)據(jù)
request.source(JSON.toJSONString(user), XContentType.JSON);
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
System.out.println(response.status());
}
我們這里以將對(duì)象轉(zhuǎn)成 JSON 串再添加,這種相對(duì)簡單通用些。添加時(shí)如果不提供文檔 id,ES 會(huì)給一個(gè)默認(rèn)值,這里為了方便后邊演示就指定了文檔 id。
官方還提供了其它方式,可以查看文檔。
如果需要批量添加我們可以使用BulkRequest類來完成,具體的實(shí)現(xiàn)如下:
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("復(fù)旦");
User user4 = new User();
user4.setName("張三豐");
user4.setAge(80);
user4.setBirthday("1940-08-15");
user4.setSchool("復(fù)旦");
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());
}
接下來通過單元測試創(chuàng)建索引、添加文檔:
@RunWith(SpringRunner.class)
@SpringBootTest
class LearnElasticsearchApplicationTests {
@Autowired
UserService userService;
@Test
void testES() throws IOException {
userService.createIndex2();
userService.addDocument();
userService.bulkAddDocument();
}
}
最終在 head 工具中可以看到如下數(shù)據(jù):

5、修改文檔
可以根據(jù)文檔 id 修改文檔,修改前可以判斷文檔是否存在:
public boolean existsDocument() throws IOException {
GetRequest request = new GetRequest("user", "1");
// 不獲取_source的內(nèi)容
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());
}
根據(jù)文檔 id 修改的局限性還是太強(qiáng)了,我們還可以使用UpdateByQueryRequest根據(jù)查詢條件來批量修改文檔:
public void updateDocument2() throws IOException {
UpdateByQueryRequest request = new UpdateByQueryRequest("user");
// 設(shè)置查詢條件
request.setQuery(new MatchPhraseQueryBuilder("name", "張三"));
// request.setQuery(new TermQueryBuilder("name.keyword", "張三"));
// 設(shè)置一次可以批處理的文檔數(shù),默認(rèn)1000
request.setBatchSize(200);
// 更新后刷新索引
request.setRefresh(true);
// 通過腳本設(shè)置如何更新
request.setScript(new Script("ctx._source.school = '復(fù)旦'"));
BulkByScrollResponse response = client.updateByQuery(request, RequestOptions.DEFAULT);
System.out.println("修改的文檔數(shù):" + response.getStatus().getUpdated());
}
6、刪除文檔
首先可以根據(jù)文檔 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");
// 設(shè)置查詢條件,查詢school是復(fù)旦的
request.setQuery(new TermQueryBuilder("school.keyword", "復(fù)旦"));
// request.setQuery(new MatchPhraseQueryBuilder("school", "復(fù)旦"));
// 設(shè)置一次可以批處理的文檔數(shù),默認(rèn)1000
request.setBatchSize(200);
// 更新后刷新索引
request.setRefresh(true);
BulkByScrollResponse response = client.deleteByQuery(request, RequestOptions.DEFAULT);
System.out.println("刪除的文檔數(shù):" + response.getStatus().getDeleted());
}