「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」
客户端介绍
在elasticsearch官网中提供了各种语言的客户端:www.elastic.co/guide/en/el…
注意点击进入后,选择版本到 6.2.4 ,因为我们之前按照的都是 6.2.4 版本:
创建Demo工程
初始化项目
pom文件
注意,这里我们直接导入了SpringBoot的启动器,方便后续讲解。不过还需要手动引入elasticsearch的High-level-Rest-Client的依赖:
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
<!--Apache开源组织提供的用于操作JAVA BEAN的工具包-->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.1</version>
</dependency>
<!--ES高级Rest Client-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>6.4.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
配置文件
我们在resource下创建application.yml
索引库及映射
创建索引库的同时,我们也会创建type及其映射关系,但是这些操作不建议使用java客户端完成,原因如下:
- 索引库和映射往往是初始化时完成,不需要频繁操作,不如提前配置好
- 官方提供的创建索引库及映射API非常繁琐,需要通过字符串拼接json结构:
因此,这些操作建议还是使用我们昨天学习的Rest风格API去实现。
我们接下来以这样一个商品数据为例来创建索引库:
package com.lagou.es.pojo;
public class Product {
private Long id;
private String title; //标题
private String category;// 分类
private String brand; // 品牌
private Double price; // 价格
private String images; // 图片地址
}
分析一下数据结构:
- id:可以认为是主键,将来判断数据是否重复的标示,不分词,可以使用keyword类型
- title:搜索字段,需要分词,可以用text类型
- category:商品分类,这个是整体,不分词,可以使用keyword类型
- brand:品牌,与分类类似,不分词,可以使用keyword类型
- price:价格,这个是double类型
- images:图片,用来展示的字段,不搜索,index为false,不分词,可以使用keyword类型
我们可以编写这样的映射配置:
PUT /lagou
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 1
},
"mappings": {
"item": {
"properties": {
"id": {
"type": "keyword"
},
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"category": {
"type": "keyword"
},
"brand": {
"type": "keyword"
},
"images": {
"type": "keyword",
"index": false
},
"price": {
"type": "double"
}
}
}
}
}
索引数据操作
有了索引库,我们接下来看看如何新增索引数据
操作MYSQL数据库:
- 获取数据库连接
- 完成数据的增删改查
- 释放资源
初始化客户端
完成任何操作都需要通过HighLevelRestClient客户端,看下如何创建。
我们先编写一个测试类:
然后再@Before的方法中编写client初始化:
public class ElasticSearchTest {
private RestHighLevelClient client;
// Json工具
private Gson gson = new Gson();
@Before
public void init(){
// 初始化HighLevel客户端
client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1", 9201, "http"),
new HttpHost("127.0.0.1", 9202, "http"),
new HttpHost("127.0.0.1", 9203, "http")
)
);
}
@After
public void close() throws IOException {
// 关闭客户端
client.close();
}
}
新增文档
@Test
public void testInsert() throws IOException {
//1.文档数据
Product product = new Product();
product.setBrand("华为");
product.setCategory("手机");
product.setId(1L);
product.setImages("http://image.huawei.com/1.jpg");
product.setPrice(5999.99);
product.setTitle("华为P50就是棒");
//2.将文档数据转换为json格式
String source = gson.toJson(product);
//3.创建索引请求对象 访问哪个索引库、哪个type、指定文档ID
//public IndexRequest(String index, String type, String id)
IndexRequest request = new IndexRequest("lagou","item",product.getId().toString());
request.source(source, XContentType.JSON);
//4.发出请求
IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
System.out.println(response);
}
查看文档
根据rest风格,查看应该是根据id进行get查询,难点是对结果的解析:
@Test
public void testFindIndex() throws IOException {
// 创建get请求,并指定id
GetRequest request = new GetRequest("lagou", "item", "1");
// 查询,得到响应
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 解析响应,应该是json
String source = response.getSourceAsString();
// 转换json数据
Product item = gson.fromJson(source, Product.class);
System.out.println(item);
}
修改文档
新增时,如果传递的id是已经存在的,则会完成修改操作,如果不存在,则是新增。
删除文档
根据id删除:
@Test
public void testDeleteIndex() throws IOException {
// 准备删除的请求,参数为id
DeleteRequest request = new DeleteRequest("lagou", "item", "1");
// 发起请求
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
System.out.println("response = " + response);
}
搜索数据
查询所有match_all
@Test
public void testMatchAll() throws IOException {
// 创建搜索对象
SearchRequest request = new SearchRequest();
// 查询构建工具
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 添加查询条件,通过QueryBuilders获取各种查询
sourceBuilder.query(QueryBuilders.matchAllQuery());
request.source(sourceBuilder);
// 搜索
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 解析
SearchHits hits = response.getHits();
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
// 取出source数据
String json = hit.getSourceAsString();
// 反序列化
Product item = gson.fromJson(json, Item.class);
System.out.println("item = " + item);
}
}
注意,上面的代码中,搜索条件是通过 sourceBuilder.query(QueryBuilders.matchAllQuery())来添加的。这个 query() 方法接受的参数是: QueryBuilder 接口类型。
这个接口提供了很多实现类,分别对应我们在之前中学习的不同类型的查询,例如:term查询、match查询、range查询、boolean查询等
因此,我们如果要使用各种不同查询,其实仅仅是传递给 sourceBuilder.query() 方法的参数不同而已。而这些实现类不需要我们去 new ,官方提供了 QueryBuilders 工厂帮我们构建各种实现类
关键字搜索match
其实搜索类型的变化,仅仅是利用QueryBuilders构建的查询对象不同而已,其他代码基本一致:
// 添加查询条件,通过QueryBuilders获取各种查询
sourceBuilder.query(QueryBuilders.matchQuery("title","手机"));
因此,我们可以把这段代码封装,然后把查询条件作为参数传递:
private void basicQuery(SearchSourceBuilder sourceBuilder) throws IOException {
// 创建搜索对象
SearchRequest request = new SearchRequest();
request.source(sourceBuilder);
// 搜索
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// 解析
SearchHits hits = response.getHits();
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
// 取出source数据
String json = hit.getSourceAsString();
// 反序列化
Product item = gson.fromJson(json, Item.class);
System.out.println("item = " + item);
}
}
调用封装的方法,并传递查询条件:
@Test
public void testMatchQuery() throws IOException {
// 查询构建工具
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 添加查询条件,通过QueryBuilders获取各种查询
sourceBuilder.query(QueryBuilders.matchQuery("title", "手机"));
basicQuery(sourceBuilder);
}
范围查询range
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price");
与页面上一样,支持下面的范围关键字:
方法 | 说明 |
---|---|
gt(Object from) | 大于 |
gte(Object from) | 大于等于 |
lt(Object from) | 小于 |
lte(Object from) | 小于等于 |
示例:
@Test
public void testRangeQuery() throws IOException {
// 查询构建工具
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 添加查询条件,通过QueryBuilders获取各种查询
sourceBuilder.query(QueryBuilders.rangeQuery("price").gt(1000).lt(4000));
basicQuery(sourceBuilder);
}
source过滤
_source:存储原始文档
默认情况下,索引库中所有数据都会返回,如果我们想只返回部分字段,可以通过source fifilter来控制。
@Test
public void testSourceFilter() throws IOException {
// 创建搜索对象
SearchRequest request = new SearchRequest();
// 查询构建工具
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 添加查询条件,通过QueryBuilders获取各种查询
sourceBuilder.query(QueryBuilders.matchAllQuery());
// 添加source过滤
sourceBuilder.fetchSource(new String[]{"id", "title", "price"}, null);
basicQuery(sourceBuilder);
}
排序
依然是通过sourceBuilder来配置:
@Test
public void testSortQuery() throws IOException {
// 创建搜索对象
SearchRequest request = new SearchRequest();
// 查询构建工具
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 添加查询条件,通过QueryBuilders获取各种查询
sourceBuilder.query(QueryBuilders.matchAllQuery());
// 添加排序
sourceBuilder.sort("price", SortOrder.ASC);
basicQuery(sourceBuilder);
}
分页
分页需要视图层传递两个参数给我们:
- 当前页:page
- 每页大小:size
而elasticsearch中需要的不是当前页,而是起始位置,还好有公式可以计算出:
from-->起始位置,0表示第一条
- 起始位置:start = (page - 1) * size
- 第一页:(1-1)*5 = 0
- 第二页:(2-1)*5 = 5
代码:
@Test
public void testSortAndPageQuery() throws IOException {
// 创建搜索对象
SearchRequest request = new SearchRequest();
// 查询构建工具
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 添加查询条件,通过QueryBuilders获取各种查询
sourceBuilder.query(QueryBuilders.matchAllQuery());
// 添加排序
sourceBuilder.sort("price", SortOrder.ASC);
// 添加分页
int page = 1;
int size = 3;
int start = (page - 1) * size;
// 配置分页
sourceBuilder.from(start);
sourceBuilder.size(3);
basicQuery(sourceBuilder);
}