Java 客户端
在 Elasticsearch 中,为 java 提供了 2 种客户端,一种是 REST 风格的客户端,另一种是 Java API 的客户端。https://www.elastic.co/guide/…
1.REST 客户端
Elasticsearch 提供了 2 种 REST 客户端,一种是低级客户端,一种是高级客户端。
- Java Low Level REST Client:官方提供的低级客户端。该客户端通过 http 来连接 Elasticsearch 集群。用户在使用该客户端时需要将请求数据手动拼接成 Elasticsearch 所需 JSON 格式进行发送,收到响应时同样也需要将返回的 JSON 数据手动封装成对象。虽然麻烦,不过该客户端兼容所有的 Elasticsearch 版本。
- Java High Level REST Client:官方提供的高级客户端。该客户端基于低级客户端实现,它提供了很多便捷的 API 来解决低级客户端需要手动转换数据格式的问题。
2. 构造数据
curl -X POST "http://47.101.129.45:9200/test/house/_bulk?pretty" -H 'Content-Type: application/json' --data-binary '{"index":{"_index":"test","_type":"house"}}
{"id":"1001","title":"整租 · 南丹大楼 1 居室 7500","price":"7500"}
{"index":{"_index":"test","_type":"house"}}
{"id":"1002","title":"陆家嘴板块,精装设计一室一厅,可拎包入住诚意租。","price":"8500"}
{"index":{"_index":"test","_type":"house"}}
{"id":"1003","title":"整租 · 健安坊 1 居室 4050","price":"7500"}
{"index":{"_index":"test","_type":"house"}}
{"id":"1004","title":"整租 · 中凯城市之光 + 视野开阔 + 景色秀丽 + 拎包入住","price":"6500"}
{"index":{"_index":"test","_type":"house"}}
{"id":"1005","title":"整租 · 南京西路品质小区 21213 三轨交汇配套齐 * 拎包入住","price":"6000"}
{"index":{"_index":"test","_type":"house"}}
{"id":"1006","title":"祥康里简约风格 * 南户型拎包入住看房随时","price":"7000"}
'
3.REST 低级客户端
1)用 IDEA 创建 SpringBoot 工程 spring-elasticsearch
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>elasticsearch</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elasticsearch</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>6.5.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2)编写测试
/**
* REST 低级客户端
*/
public class TestElasticSearch {private static final ObjectMapper MAPPER = new ObjectMapper();
private RestClient restClient;
@Before
public void init() {
RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost("47.101.129.45", 9200, "http"));
restClientBuilder.setFailureListener(new RestClient.FailureListener() {
@Override
public void onFailure(Node node) {System.out.println("出错了 ->" + node);
}
});
this.restClient = restClientBuilder.build();}
@After
public void after() throws IOException {restClient.close();
}
/**
* 查询集群状态
*
* @throws IOException
*/
@Test
public void testGetInfo() throws IOException {Request request = new Request("GET", "/_cluster/state");
request.addParameter("pretty", "true");
Response response = this.restClient.performRequest(request);
System.out.println(response.getStatusLine());
System.out.println(EntityUtils.toString(response.getEntity()));
}
/**
* 新增数据
*
* @throws IOException
*/
@Test
public void testCreateData() throws IOException {Request request = new Request("POST", "/test/house");
request.addParameter("pretty", "true");
Map<String, Object> data = new HashMap<>();
data.put("id", "2001");
data.put("title", "张江高科");
data.put("price", "3500");
request.setJsonEntity(MAPPER.writeValueAsString(data));
Response response = this.restClient.performRequest(request);
System.out.println(response.getStatusLine());
System.out.println(EntityUtils.toString(response.getEntity()));
}
/**
* 根据 id 查询数据
*/
@Test
public void testQueryData() throws IOException {Request request = new Request("GET", "/test/house/3xNNOW4BpJzEX51okOM5");
request.addParameter("pretty", "true");
Response response = this.restClient.performRequest(request);
System.out.println(response.getStatusLine());
System.out.println(EntityUtils.toString(response.getEntity()));
}
/**
* 搜索数据
*/
@Test
public void testSearchData() throws IOException {Request request = new Request("POST", "/test/house/_search");
String searchJson = "{\"query\": {\"match\": {\"title\": \" 拎包入住 \"}}}";
request.setJsonEntity(searchJson);
request.addParameter("pretty", "true");
Response response = this.restClient.performRequest(request);
System.out.println(response.getStatusLine());
System.out.println(EntityUtils.toString(response.getEntity()));
}
}
从使用中,可以看出,基本和我们使用 RESTful api 使用几乎是一致的
4.REST 高级客户端
pom.xml 引入依赖
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.5.4</version>
</dependency>
编写测试
/**
* REST 高级客户端
*/
public class TestRestHighLevel {
private RestHighLevelClient client;
@Before
public void init() {
RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost("47.101.129.45", 9200, "http")
);
this.client = new RestHighLevelClient(restClientBuilder);
}
@After
public void after() throws Exception {this.client.close();
}
/**
* 新增文档,同步操作
*
* @throws Exception
*/
@Test
public void testCreate() throws Exception {Map<String, Object> data = new HashMap<>();
data.put("id", "2002");
data.put("title", "南京西路 拎包入住 一室一厅");
data.put("price", "4500");
IndexRequest indexRequest = new IndexRequest("test", "house").source(data);
IndexResponse indexResponse = this.client.index(indexRequest, RequestOptions.DEFAULT);
System.out.println(indexResponse);
System.out.println("id->" + indexResponse.getId());
System.out.println("index->" + indexResponse.getIndex());
System.out.println("type->" + indexResponse.getType());
System.out.println("version->" + indexResponse.getVersion());
System.out.println("result->" + indexResponse.getResult());
System.out.println("shardInfo->" + indexResponse.getShardInfo());
}
/**
* 新增文档,异步操作
*/
@Test
public void testCreateAsync() throws Exception {Map<String, Object> data = new HashMap<>();
data.put("id", "2003");
data.put("title", "南京东路最新房源二室一厅");
data.put("price", "5500");
IndexRequest indexRequest = new IndexRequest("test", "house").source(data);
this.client.indexAsync(indexRequest, RequestOptions.DEFAULT, new ActionListener<IndexResponse>() {
@Override
public void onResponse(IndexResponse indexResponse) {System.out.println(indexResponse);
}
@Override
public void onFailure(Exception e) {System.out.println(e);
}
});
Thread.sleep(2000);
}
/**
* 指定返回字段查询
*/
@Test
public void testQuery() throws Exception {GetRequest request = new GetRequest("test", "house", "4hN-OW4BpJzEX51oe-Of");
// 指定返回字段
String[] includes = new String[]{"title", "id"};
String[] excludes = Strings.EMPTY_ARRAY;
FetchSourceContext fetchSourceContext = new FetchSourceContext(true, includes, excludes);
request.fetchSourceContext(fetchSourceContext);
GetResponse response = this.client.get(request, RequestOptions.DEFAULT);
System.out.println("数据 ->" + response);
}
/**
* 判断是否存在
*/
@Test
public void testExists() throws Exception {GetRequest getRequest = new GetRequest("test", "house", "4hN-OW4BpJzEX51oe-Of");
// 不返回字段
getRequest.fetchSourceContext(new FetchSourceContext(false));
boolean exists = this.client.exists(getRequest, RequestOptions.DEFAULT);
System.out.println("exists ->" + exists);
}
/**
* 删除数据
*/
@Test
public void testDelete() throws Exception {DeleteRequest deleteRequest = new DeleteRequest("test", "house", "4hN-OW4BpJzEX51oe-Of");
DeleteResponse response = this.client.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(response.status());// OK or NOT_FOUND
}
/**
* 更新数据
*/
@Test
public void testUpdate() throws Exception {UpdateRequest updateRequest = new UpdateRequest("test", "house", "4BN4OW4BpJzEX51o3-PZ");
Map<String, Object> data = new HashMap<>();
data.put("title", "南京西路 2 一室一厅 2");
data.put("price", "4000");
updateRequest.doc(data);
UpdateResponse response = this.client.update(updateRequest, RequestOptions.DEFAULT);
System.out.println("version ->" + response.getVersion());
}
/**
* 查询数据
*/
@Test
public void testSearch() throws Exception {SearchRequest searchRequest = new SearchRequest("test");
searchRequest.types("house");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("title", "拎包入住"));
sourceBuilder.from(0);
sourceBuilder.size(5);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchRequest.source(sourceBuilder);
SearchResponse search = this.client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("搜索到 ->" + search.getHits().totalHits + "条数据");
SearchHits hits = search.getHits();
for (SearchHit hit : hits) {System.out.println(hit.getSourceAsString());
}
}
}
SpringBoot 整合 Elasticsearch
Spring Data 项目对 Elasticsearch 做了支持,其目的就是简化对 Elasticsearch 的操作,https://spring.io/projects/sp…。
1. 导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>elasticsearch</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>elasticsearch</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>6.5.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
<!--REST 高级客户端 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.5.4</version>
</dependency>
<!--SpringBoot 整合 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2. 编写 application.yml
spring:
application:
name: spring-elasticsearch
data:
elasticsearch:
cluster-name: docker-cluster
cluster-nodes: 47.101.129.45:9300
这里要注意,使用的端口是 9300,而并非 9200,原因是 9200 是 RESTful 端口,9300 是 API 端口。
ElasticSearch 之 ElasticsearchTemplate
3. 编写测试
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "demo", type = "person", createIndex = false)
public class Person {
/**
* 1. 索引库(indices) indices 是 index 的复数,代表许多的索引,* 2. 类型(type)类型是模拟 mysql 中的 table 概念,一个索引库下可以有不同类型的索引,比如商品索引,订单索引,其数据格式不同。不过这会导致索引库混乱,因此未来版本中会移除这个概念
* 3. 文档(document)存入索引库原始的数据。比如每一条商品信息,就是一个文档
* 4. 字段(field)文档中的属性
* 5. 映射配置(mappings)字段的数据类型、属性、是否索引、是否存储等特性
*/
/**
* @Document 作用在类,标记实体类为文档对象,一般有两个属性
* 1.indexName:对应索引库名称
* 2.type:对应在索引库中的类型
* 3.shards:分片数量,默认 5
* 4.replicas:副本数量,默认 1
* @Id 作用在成员变量,标记一个字段作为 id 主键
* @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:* 1.type:字段类型,是枚举:FieldType,可以是 text、long、short、date、integer、object 等
* 2.text:存储数据时候,会自动分词,并生成索引
* 3.keyword:存储数据时候,不会分词建立索引
* 4.Numerical:数值类型,分两类
* 基本数据类型:long、interger、short、byte、double、float、half_float
* 浮点数的高精度类型:scaled_float
* 需要指定一个精度因子,比如 10 或 100。elasticsearch 会把真实值乘以这个因子后存储,取出时再还原。* 5.Date:日期类型
* elasticsearch 可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为 long,节省空间。* 6.index:是否索引,布尔类型,默认是 true
* 7.store:是否存储,布尔类型,默认是 false
* 8.analyzer:分词器名称,这里的 ik_max_word 即使用 ik 分词器
*/
@Id
private Long id;
@Field(store = true)
private String name;
@Field
private Integer age;
@Field
private String mail;
@Field(store = true)
private String hobby;
}
1)新增数据
/**
* Spring Data ElasticSearch
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestSpringBootES {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 添加数据
*/
@Test
public void save() {User user = new User();
user.setId(1001L);
user.setName("赵柳");
user.setAge(20);
user.setHobby("足球、篮球、听音乐");
IndexQuery indexQuery = new IndexQueryBuilder()
.withObject(user).build();
String index = this.elasticsearchTemplate.index(indexQuery);
System.out.println(index);
}
}
2)批量插入
@Test
public void testBulk() {List list = new ArrayList<>();
for (int i = 0; i < 5000; i++) {User person = new User();
person.setId(1001L + i);
person.setAge(i % 50 + 10);
person.setName("张三" + i);
person.setHobby("足球、篮球、听音乐");
IndexQuery indexQuery = new IndexQueryBuilder().withObject(person).build();
list.add(indexQuery);
}
Long start = System.currentTimeMillis();
this.elasticsearchTemplate.bulkIndex(list);
System.out.println("用时:" + (System.currentTimeMillis() - start));
}
3)局部更新,全部更新使用 index 覆盖即可
@Test
public void testUpdate() {IndexRequest indexRequest = new IndexRequest();
indexRequest.source("age", "30");
UpdateQuery updateQuery = new UpdateQueryBuilder()
.withId("1002")
.withClass(User.class)
.withIndexRequest(indexRequest).build();
UpdateResponse response = this.elasticsearchTemplate.update(updateQuery);
System.out.println(response);
}
4)删除
@Test
public void testDelete() {String result = this.elasticsearchTemplate.delete(User.class, "1002");
System.out.println(result);
}
5)查询
@Test
public void testSearch() {PageRequest pageRequest = PageRequest.of(0, 10);// 设置分页参数
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("name", "赵柳"))//match 查询
.withPageable(pageRequest)
.build();
AggregatedPage<User> persons = this.elasticsearchTemplate.queryForPage(searchQuery, User.class);
System.out.println("persons ->" + persons);
System.out.println("总页数:" + persons.getTotalPages()); // 获取总页数
List<User> content = persons.getContent();// 获取搜索到的数据
for (User p : content) {System.out.println(p);
}
}
ElasticSearch 之 ElasticsearchRepository
3. 编写测试
1)创建实体 Pojo
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "commodity", type = "docs", shards = 1, replicas = 0)
public class Commodity {
@Id
private Long id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title; // 标题
@Field(type = FieldType.Keyword)
private String category;// 分类
@Field(type = FieldType.Keyword)
private String brand; // 品牌
@Field(type = FieldType.Double)
private Double price; // 价格
@Field(index = false, type = FieldType.Keyword)
private String images; // 图片地址
}
2)继承 ElasticsearchRepository
import com.example.elasticsearch.pojo.Commodity;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
public interface CommodityRepository extends ElasticsearchRepository<Commodity, Long> {
}
3)测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestSpringBootES2 {
@Resource
private CommodityRepository commodityRepository;
/**
* 创建索引
*/
@Test
public void createIndex() {boolean index = elasticsearchTemplate.createIndex(Commodity.class);
System.out.println(index);
}
/**
* 添加数据
*/
@Test
public void testInsert() {
Commodity commodity = new Commodity(1L, "小米手机 7", "手机",
"小米", 3499.00, "http://image.baidu.com/13123.jpg");
Commodity save = commodityRepository.save(commodity);
System.out.println(save);
}
}