Java操作ElasticSearch
需要注意ES暴露的http服务端口是9200,TCP通讯端口是9300,也就是Javaclient操作ES需要连接9300端口。
0. 简介
Java项目中操作ES可以用ES的客户端 TransportClient、RestClient; springboot项目可以用Spring Data Elasticsearch(内部也是封装了RestClient)。
1. TransportClient
TransportClient 是ElasticSearch(java)客户端封装对象,使用transport远程连接到Elasticsearch集群,默认用的TCP端口是9300,该transport node并不会加入集群,而是简单的向ElasticSearch集群上的节点发送请求。
2. Rest ClientJava REST客户端有两种风格:
Java Low Level REST Client:elasticsearch client 低级别客户端。它允许通过http请求与Elasticsearch集群进行通信。API本身不负责数据的编码解码,由用户去编码解码。它与所有的ElasticSearch版本兼容。
Java High Level REST Client:Elasticsearch client官方高级客户端。基于低级客户端,它定义的API,已经对请求与响应数据包进行编码解码。
Elasticsearch计划在Elasticsearch 7.0中弃用TransportClient,在8.0中完全删除它。故在实际使用过程中建议使用Java高级REST client。Rest client执行HTTP请求来执行操作,无需再序列化的Java请求。
参考网站:https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/index.html
1.pom配置如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>cn.qlq</groupId> <artifactId>esclient</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> <maven.compiler.source>1.8</maven.compiler.source> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>7.8.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.11.1</version> </dependency> </dependencies> </project>
resources下新建log4j2.properties
appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c] %marker%m%n
rootLogger.level = info
rootLogger.appenderRef.console.ref = console
2.API测试 (基于TransportClient)
其API操作过程如下:
1.创建client
单机版如下:
// on startup TransportClient client = new PreBuiltTransportClient(Settings.EMPTY) .addTransportAddress(new TransportAddress(InetAddress.getByName("host1"), 9300)) .addTransportAddress(new TransportAddress(InetAddress.getByName("host2"), 9300));
集群如下:
Settings settings = Settings.builder() .put("cluster.name", "myClusterName").build(); TransportClient client = new PreBuiltTransportClient(settings); //Add transport addresses and do something with the client...
TransportClient 进行操作的时候可以指定操作的索引类型、以及ID等操作,例如:
查询时候指定索引类型:(prepareSearch()接收的是可变参数,可以指定多个索引类型搜索。不指定默认是查询所有类型)
SearchRequestBuilder srb1 = client.prepareSearch("orders").setQuery(QueryBuilders.queryStringQuery("qiao"))
.setSize(1);
创建时指定类型:(ID不指定ES会生成)
IndexResponse response = client.prepareIndex("orders", "order").setSource(builder).get();
2.操作
进行CRUD
3.关闭client
// on shutdown client.close();
0.准备工作
1.修改EK/conf/elasticsearch.yml下面的集群名称
cluster.name: my-application
2.启动两个节点
elasticsearch.bat -Ehttp.port=9200 -Epath.data=E:/data/0
elasticsearch.bat -Ehttp.port=19200 -Epath.data=E:/data/1
3.查看节点
访问:http://localhost:9200/_cluster/stats?pretty
1.文档API
1.创建索引文档
创建文档构造数据有4种方式,如下:
Manually (aka do it yourself) using native byte[] or as a String
Using a Map that will be automatically converted to its JSON equivalent
Using a third party library to serialize your beans such as Jackson
Using built-in helpers XContentFactory.jsonBuilder()
第一种:使用ES helper构造
private static void createDocument() throws UnknownHostException, IOException { // on startup Settings settings = Settings.builder().put("cluster.name", "my-application").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); // 构造数据 // 第一种:使用es heler XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("username", "qiaozhi") .field("fullname", "乔治").field("created", new Date()).field("deleted", false).endObject(); IndexResponse response = client.prepareIndex("accounts", "person", "1").setSource(builder).get(); // 第二种: 自己构造JSON数据 // IndexResponse response = client.prepareIndex("twitter", "_doc") // .setSource(json, XContentType.JSON) // .get(); // 打印保存信息 // Index name String _index = response.getIndex(); System.out.println("_index " + _index); // Type name String _type = response.getType(); System.out.println("_type " + _type); // Document ID (generated or not) String _id = response.getId(); System.out.println("_id " + _id); // Version (if it's the first time you index this document, you will // get: 1) long _version = response.getVersion(); System.out.println("_version " + _version); // status has stored current instance statement. RestStatus status = response.status(); System.out.println("status " + status); // on shutdown client.close(); }
结果:
_index accounts
_type person
_id 1
_version 2
status OK
第二种:手动构造JSON数据且不指定ID会生成ID
private static void createDocument() throws UnknownHostException, IOException { // on startup Settings settings = Settings.builder().put("cluster.name", "my-application").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); // 构造数据 String json = "{" + "\"username\":\"zhangsan\"," + "\"fullname\":\"张三\"," + "\"deleted\":\"false\"," + "\"created\":\"2020-02-05\"" + "}"; // 保存文档不指定ID IndexResponse response = client.prepareIndex("accounts", "person").setSource(json, XContentType.JSON).get(); // 打印保存信息 // Index name String _index = response.getIndex(); System.out.println("_index " + _index); // Type name String _type = response.getType(); System.out.println("_type " + _type); // Document ID (generated or not) String _id = response.getId(); System.out.println("_id " + _id); // Version (if it's the first time you index this document, you will // get: 1) long _version = response.getVersion(); System.out.println("_version " + _version); // status has stored current instance statement. RestStatus status = response.status(); System.out.println("status " + status); // on shutdown client.close(); }
结果:
_index accounts
_type person
_id nvyXyXMB58D4pLOfTCzx
_version 1
status CREATED
使用kibana进行查询:
GET accounts/person/nvyXyXMB58D4pLOfTCzx
#! Deprecation: [types removal] Specifying types in document get requests is deprecated, use the /{index}/_doc/{id} endpoint instead. { "_index" : "accounts", "_type" : "person", "_id" : "nvyXyXMB58D4pLOfTCzx", "_version" : 1, "_seq_no" : 7, "_primary_term" : 3, "found" : true, "_source" : { "username" : "zhangsan", "fullname" : "张三", "deleted" : "false", "created" : "2020-02-05" } }
补充:如果已经存在相同ID的数据会进行修改操作,比如下面:
// 构造数据 String json = "{" + "\"username\":\"zhangsan2\"," + "\"fullname\":\"张三2\"," + "\"deleted\":\"false\"," + "\"created\":\"2020-02-05\"" + "}"; // 保存文档不指定ID IndexResponse response = client.prepareIndex("accounts", "person", "nvyXyXMB58D4pLOfTCzx") .setSource(json, XContentType.JSON).get();
结果:
_index accounts
_type person
_id nvyXyXMB58D4pLOfTCzx
_version 2
status OK
kibana查看数据如下:
#! Deprecation: [types removal] Specifying types in document get requests is deprecated, use the /{index}/_doc/{id} endpoint instead. { "_index" : "accounts", "_type" : "person", "_id" : "nvyXyXMB58D4pLOfTCzx", "_version" : 2, "_seq_no" : 8, "_primary_term" : 3, "found" : true, "_source" : { "username" : "zhangsan2", "fullname" : "张三2", "deleted" : "false", "created" : "2020-02-05" } }
补充:测试集群中两个节点都有数据
liqiang@root MINGW64 ~/Desktop $ curl http://localhost:9200/accounts/person/nvyXyXMB58D4pLOfTCzx % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 220 100 220 0 0 13750 0 --:--:-- --:--:-- --:--:-- 214k{"_index":"accounts","_type":"person","_id":"nvyXyXMB58D4pLOfTCzx","_version":2,"_seq_no":8,"_primary_term":3,"found":true,"_source":{"username":"zhangsan2","fullname":"张三2","deleted":"false","created":"2020-02-05"}} liqiang@root MINGW64 ~/Desktop $ curl http://localhost:19200/accounts/person/nvyXyXMB58D4pLOfTCzx % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 220 100 220 0 0 7096 0 --:--:-- --:--:-- --:--:-- 14666{"_index":"accounts","_type":"person","_id":"nvyXyXMB58D4pLOfTCzx","_version":2,"_seq_no":8,"_primary_term":3,"found":true,"_source":{"username":"zhangsan2","fullname":"张三2","deleted":"false","created":"2020-02-05"}}
2.查询
private static void getDocument() throws UnknownHostException { // on startup Settings settings = Settings.builder().put("cluster.name", "my-application").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); // 根据ID查询 GetResponse response = client.prepareGet("accounts", "person", "nvyXyXMB58D4pLOfTCzx").get(); // 打印获取信息 // Index name String _index = response.getIndex(); System.out.println("_index " + _index); // Type name String _type = response.getType(); System.out.println("_type " + _type); // Document ID (generated or not) String _id = response.getId(); System.out.println("_id " + _id); // Version (if it's the first time you index this document, you will // get: 1) long _version = response.getVersion(); System.out.println("_version " + _version); // 获取存的信息 String sourceAsString = response.getSourceAsString(); System.out.println("sourceAsString " + sourceAsString); Map<String, Object> sourceAsMap = response.getSourceAsMap(); System.out.println("sourceAsMap " + sourceAsMap); // on shutdown client.close(); }
结果:
_index accounts
_type person
_id nvyXyXMB58D4pLOfTCzx
_version 2
sourceAsString {"username":"zhangsan2","fullname":"张三2","deleted":"false","created":"2020-02-05"}
sourceAsMap {deleted=false, created=2020-02-05, fullname=张三2, username=zhangsan2}
批量查询:
再创建一个订单类型的文档数据。(这里需要注意,不允许一个index下面有多个type)
liqiang@root MINGW64 ~/Desktop $ curl http://localhost:9200/orders/order/HSJgznMBk9PkhN4HiuEb % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 233 100 233 0 0 7516 0 --:--:-- --:--:-- --:--:-- 15533{"_index":"orders","_type":"order","_id":"HSJgznMBk9PkhN4HiuEb","_version":1,"_seq_no":0,"_primary_term":1,"found":true,"_source":{"desc":"测试订单","createTime":"2020-08-08T14:01:37.160Z","deleted":false,"username":"zhangsan2"}}
批量查询语法如下:
private static void batchSelect() throws Exception { // on startup Settings settings = Settings.builder().put("cluster.name", "my-application").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); // 查询多个类型的数据(接受可变类型的ID参数) MultiGetResponse multiGetItemResponses = client.prepareMultiGet() .add("accounts", "person", "nvyXyXMB58D4pLOfTCzx") .add("orders", "order", "HSJgznMBk9PkhN4HiuEb", "otherId").get(); for (MultiGetItemResponse itemResponse : multiGetItemResponses) { GetResponse response = itemResponse.getResponse(); if (response.isExists()) { String json = response.getSourceAsString(); System.out.println(json); } } // on shutdown client.close(); }
结果:
{"username":"zhangsan2","fullname":"修改后","deleted":"false","created":"2020-02-05"}
{"desc":"测试订单","createTime":"2020-08-08T14:01:37.160Z","deleted":false,"username":"zhangsan2"}
3.删除文档
private static void deleteDoc() throws UnknownHostException { // on startup Settings settings = Settings.builder().put("cluster.name", "my-application").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); // 根据ID删除 DeleteResponse response = client.prepareDelete("accounts", "person", "nvyXyXMB58D4pLOfTCzx").get(); // 打印获取信息 // Index name String _index = response.getIndex(); System.out.println("_index " + _index); // Type name String _type = response.getType(); System.out.println("_type " + _type); // Document ID (generated or not) String _id = response.getId(); System.out.println("_id " + _id); // Version (if it's the first time you index this document, you will // get: 1) long _version = response.getVersion(); System.out.println("_version " + _version); // on shutdown client.close(); }
结果:
_index accounts
_type person
_id nvyXyXMB58D4pLOfTCzx
_version 3
删除后再次查询:
$ curl http://localhost:9200/accounts/person/nvyXyXMB58D4pLOfTCzx % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 81 100 81 0 0 2531 0 --:--:-- --:--:-- --:--:-- 5062{"_index":"accounts","_type":"person","_id":"nvyXyXMB58D4pLOfTCzx","found":false}
也可以根据查询结果进行删除:
BulkByScrollResponse response = new DeleteByQueryRequestBuilder(client, DeleteByQueryAction.INSTANCE) .filter(QueryBuilders.matchQuery("fullname", "修改后")).source("accounts").get(); long deleted = response.getDeleted();
4.修改文档:
例如已经存在的文档:
$ curl http://localhost:9200/accounts/person/nvyXyXMB58D4pLOfTCzx % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 221 100 221 0 0 523 0 --:--:-- --:--:-- --:--:-- 565{"_index":"accounts","_type":"person","_id":"nvyXyXMB58D4pLOfTCzx","_version":1,"_seq_no":10,"_primary_term":4,"found":true,"_source":{"username":"zhangsan2","fullname":"张三2","deleted":"false","created":"2020-02-05"}}
(1)重新插入带ID的文档就是修改
(2)使用UpdateRequest
UpdateRequest updateRequest = new UpdateRequest(); updateRequest.index("accounts"); updateRequest.type("person"); updateRequest.id("nvyXyXMB58D4pLOfTCzx"); updateRequest.doc(XContentFactory.jsonBuilder().startObject().field("fullname", "修改后").endObject()); UpdateResponse updateResponse = client.update(updateRequest).get();
2. 查询API
首先准备十条测试数据,如下:
private static void createDocument() throws UnknownHostException, IOException { // on startup Settings settings = Settings.builder().put("cluster.name", "my-application").build(); TransportClient client = new PreBuiltTransportClient(settings) .addTransportAddress(new TransportAddress(InetAddress.getByName("127.0.0.1"), 9300)); for (int i = 0; i < 10; i++) { // 构造数据 // 第一种:使用es heler XContentBuilder builder = XContentFactory.jsonBuilder().startObject().field("desc", "测试订单" + i) .field("createTime", new Date()).field("deleted", false).field("username", "zhangsan").endObject(); IndexResponse response = client.prepareIndex("orders", "order").setSource(builder).get(); // 打印保存信息 // Index name String _index = response.getIndex(); System.out.println("_index " + _index); // Type name String _type = response.getType(); System.out.println("_type " + _type); // Document ID (generated or not) String _id = response.getId(); System.out.println("_id " + _id); // Version (if it's the first time you index this document, you will // get: 1) long _version = response.getVersion(); System.out.println("_version " + _version); // status has stored current instance statement. RestStatus status = response.status(); System.out.println("status " + status); } // on shutdown client.close(); }
1. 滚动查询,类似于分页查询
QueryBuilder qb = QueryBuilders.termQuery("username", "zhangsan"); SearchResponse scrollResp = client.prepareSearch("orders") .addSort(FieldSortBuilder.DOC_FIELD_NAME, SortOrder.ASC).setScroll(new TimeValue(60000)).setQuery(qb) .setSize(3).get(); // Scroll until no hits are returned int startPage = 1; do { System.out.println("开始分页===" + (startPage++)); for (SearchHit hit : scrollResp.getHits().getHits()) { // Handle the hit... String sourceAsString = hit.getSourceAsString(); System.out.println(sourceAsString); } scrollResp = client.prepareSearchScroll(scrollResp.getScrollId()).setScroll(new TimeValue(60000)).execute() .actionGet(); } while (scrollResp.getHits().getHits().length != 0);
结果:
开始分页===1
{"desc":"测试订单0","createTime":"2020-08-09T02:31:18.435Z","deleted":false,"username":"zhangsan"}
{"desc":"测试订单1","createTime":"2020-08-09T02:31:19.455Z","deleted":false,"username":"zhangsan"}
{"desc":"测试订单2","createTime":"2020-08-09T02:31:19.655Z","deleted":false,"username":"zhangsan"}
开始分页===2
{"desc":"测试订单3","createTime":"2020-08-09T02:31:19.819Z","deleted":false,"username":"zhangsan"}
{"desc":"测试订单4","createTime":"2020-08-09T02:31:20.312Z","deleted":false,"username":"zhangsan"}
{"desc":"测试订单5","createTime":"2020-08-09T02:31:20.402Z","deleted":false,"username":"zhangsan"}
开始分页===3
{"desc":"测试订单6","createTime":"2020-08-09T02:31:20.536Z","deleted":false,"username":"zhangsan"}
{"desc":"测试订单7","createTime":"2020-08-09T02:31:20.667Z","deleted":false,"username":"zhangsan"}
{"desc":"测试订单8","createTime":"2020-08-09T02:31:20.866Z","deleted":false,"username":"zhangsan"}
开始分页===4
{"desc":"测试订单9","createTime":"2020-08-09T02:31:21.137Z","deleted":false,"username":"zhangsan"}
2. MultiSearch 多个条件查询(不指定查询的索引默认查询所有的)
SearchRequestBuilder srb1 = client.prepareSearch().setQuery(QueryBuilders.queryStringQuery("qiao")).setSize(1); SearchRequestBuilder srb2 = client.prepareSearch().setQuery(QueryBuilders.matchQuery("username", "zhangsan2")) .setSize(1); MultiSearchResponse sr = client.prepareMultiSearch().add(srb1).add(srb2).get(); // You will get all individual responses from // MultiSearchResponse#getResponses() for (MultiSearchResponse.Item item : sr.getResponses()) { SearchResponse response = item.getResponse(); SearchHits hits = response.getHits(); for (SearchHit hit : hits) { System.out.println(hit.getSourceAsString()); } }
结果:
{
"name": "zhi",
"lastName": "qiao",
"job": "enginee"
}
{"desc":"测试订单","createTime":"2020-08-08T14:01:37.160Z","deleted":false,"username":"zhangsan2"}
3.Query DSL 以及聚合查询
下一篇介绍。
补充:使用Java高级REST client的例子如下
private static void restClientQuery() throws IOException { // on startup RestHighLevelClient client = new RestHighLevelClient( RestClient.builder(new HttpHost("localhost", 9200, "http"), new HttpHost("localhost", 19200, "http"))); MultiSearchRequest request = new MultiSearchRequest(); SearchRequest firstSearchRequest = new SearchRequest(); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.matchQuery("name", "qiao")); firstSearchRequest.source(searchSourceBuilder); request.add(firstSearchRequest); SearchRequest secondSearchRequest = new SearchRequest(); searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.query(QueryBuilders.matchQuery("username", "zhangsan2")); secondSearchRequest.source(searchSourceBuilder); request.add(secondSearchRequest); // 2. 打印查询结果 MultiSearchResponse sr = client.msearch(request, RequestOptions.DEFAULT); // You will get all individual responses from // MultiSearchResponse#getResponses() for (MultiSearchResponse.Item item : sr.getResponses()) { SearchResponse response = item.getResponse(); SearchHits hits = response.getHits(); for (SearchHit hit : hits) { System.out.println(hit.getSourceAsString()); } } // on shutdown client.close(); }
结果:
{"username":"zhangsan2","fullname":"张三2","sex":1,"userid":3,"birth":"2020-08-09T09:29:44.832Z"}
{"username":"zhangsan2","fullname":"张三2","createTime":"2020-08-09T04:11:42.547Z","deleted":false,"sex":1}
{"amount":2,"createTime":"2020-08-09T11:09:07.789Z","description":"订单描述2","orderid":3,"ordernum":"order2","username":"zhangsan2"}
{"amount":7,"createTime":"2020-08-09T11:09:13.823Z","description":"订单描述7","orderid":8,"ordernum":"order7","username":"zhangsan2"}
中文API文档参考:https://www.wenjiangs.com/doc/auwvbcpq1