一、Java操作索引库(了解)
1. 使用步骤
- 导入ES的客户端依赖坐标
- 把RestHighLevelClient对象放到IoC容器里:在配置类里或者在引导类里添加
- 操作RestAPI
- 导入es的客户端依赖坐标
注意:es客户端依赖的版本号,必须要和es的版本号一致
<!--es客户端-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.0</version>
</dependency>
- 把RestHighLevelClient对象放到IoC容器里:在配置类里或者在引导类里添加
@SpringBootApplication
@MapperScan("com.sdf.mapper")
public class HotelApplication {
public static void main(String[] args) {
SpringApplication.run(HotelApplication.class, args);
}
//把RestHighLevelClient对象放到IoC容器里
@Bean
public RestHighLevelClient esClient(){
return new RestHighLevelClient(RestClient.builder(
new HttpHost("192.168.119.129",9200)
));
}
}
- 操作RestAPI
操作RestAPI步骤
创建RestHighLevelClient对象
使用RestHighLevelClient操作es
关闭RestHighLevelClient
2. 操作索引库
RestHighLevelClient对象提供了操作索引库的API方法,常用的有:
-
创建:
restClient.indices().create(CreateIndexRequest request, RequestOptions options) -
删除:
restClient.indices().delete(DeleteIndexRequest request, RequestOptions options) -
是否存在:
restClient.-().exists(GetIndexRequest request, RequestOptions options)
以上方法的参数:
CreateIndexRequest:创建索引库的请求对象DeleteIndexRequest:删除索引库的请求对象GetIndexRequest:查询索引库的请求对象RequestOptions:创建索引库的请求选项,使用固定值RequestOptions.DEFAULT即可
注意事项:
- 使用RestAPI创建索引库非常繁琐,建议在Kibana里使用DSL语句执行操作,而不是用Java代码创建索引库
3. 示例
@SpringBootTest
public class Demo01IndexTest {
@Autowired
private RestHighLevelClient esClient;
@Test
//创建索引库
public void testCreateIndex() throws IOException {
String jsonStr = "{\n" +
" \"mappings\": {\n" +
" \"properties\": {\n" +
" \"id\":{\n" +
" \"type\": \"long\"\n" +
" },\n" +
" \"name\":{\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\"\n" +
" },\n" +
" \"price\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"stock\":{\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"image\":{\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
//注意,使用的是:org.elasticsearch.client.indices.CreateIndexRequest
CreateIndexRequest request = new CreateIndexRequest("product")
.source(jsonStr, XContentType.JSON);
esClient.indices().create(request, RequestOptions.DEFAULT);
}
//删除索引库
@Test
public void testDeleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("product");
esClient.indices().delete(request, RequestOptions.DEFAULT);
}
//索引库是否存在
@Test
public void testExistsIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("product");
boolean exists = esClient.indices().exists(request, RequestOptions.DEFAULT);
System.out.println("exists = " + exists);
}
}
二、Java操作文档
1. API说明
使用Java操作文档,和在kibana里编写DSl非常类似。只是由Java代码来执行相同的操作
RestHighLevelClient提供了操作文档的API如下:
- 新增与修改:
client.index(IndexRequest request, RequestOptions options) - 查看:
client.get(GetRequest request, RequestOptions options) - 删除:
client.delete(DeleteRequest request, RequestOptions options)
以上方法的参数:
IndexRequest:新增与修改文档的请求对象GetRequest:查询文档的请求对象DeleteRequest:删除文档的请求对象RequestOptions:请求选项参数,使用固定值RequestOptions.DEFAULT即可
2. 示例
public class Demo02Document {
@Autowired
private RestHighLevelClient esClient;
/**
* 保存文档
* 如果id不存在,是新增
* 如果id已存在,是修改
*/
@Test
public void testSaveDocument() throws IOException {
Product product = new Product(1L, "华为 Mate40 pro", "手机数码", "华为", 5200D, "http://images.huawei.com/pro40.jpg");
String productJson = JSON.toJSONString(product);
//1. 准备Request对象,设置索引库名称
IndexRequest indexRequest = new IndexRequest("product")
//设置文档id
.id(product.getId().toString())
//设置文档数据
.source(productJson, XContentType.JSON);
//2. 发送请求
client.index(indexRequest, RequestOptions.DEFAULT);
}
/**
* 查看文档
*/
@Test
public void testFindDocument() throws IOException {
//1. 准备Request
GetRequest getRequest = new GetRequest("product", "1");
//2. 发送请求
GetResponse response = client.get(getRequest, RequestOptions.DEFAULT);
//3. 处理响应结果
String docJson = response.getSourceAsString();
Product product = JSON.parseObject(docJson, Product.class);
System.out.println(product);
}
/**
* 删除文档
*/
@Test
public void testDeleteDocument() throws IOException {
//1. 准备Request
DeleteRequest deleteRequest = new DeleteRequest("product", "1");
//2. 发送请求
client.delete(deleteRequest, RequestOptions.DEFAULT);
}
}
3. 批量操作文档
3.1 说明
批量操作的核心在于将多个普通请求封装在一个批量请求中,一次性发送到服务器,下面仅仅演示批量新增(其他自己尝试)
操作的步骤:
- 准备BulkRequest对象
- 把每个文档数据,封装到一个IndexRequest对象里。把IndexRequest添加到BulkRequest对象中
- 使用client.bulk(bulkRequest对象, RequestOptions.DEFAULT)
3.2 示例
/**
* 批量操作文档
*/
@Test
public void testBulkDocument() throws IOException {
// 模拟数据
List<Product> list = new ArrayList<Product>();
list.add(new Product(11L, "小米手机", "手机数码", "小米", 3299.00, "http://image.huawei.com/13123.jpg"));
list.add(new Product(12L, "锤子手机", "手机数码", "锤子", 3699.00, "http://image.huawei.com/13123.jpg"));
list.add(new Product(13L, "联想手机", "手机数码", "联想", 4499.00, "http://image.huawei.com/13123.jpg"));
list.add(new Product(14L, "红米手机", "手机数码", "小米", 4299.00, "http://image.huawei.com/13123.jpg"));
list.add(new Product(15L, "iPhone X", "手机数码", "苹果", 8299.00, "http://image.huawei.com/13123.jpg"));
list.add(new Product(16L, "MacBook pro", "电脑", "苹果", 14999.00, "http://image.huawei.com/13123.jpg"));
list.add(new Product(17L, "AirPods", "手机数码", "苹果", 4299.00, "http://image.huawei.com/13123.jpg"));
list.add(new Product(18L, "外星人", "电脑", "DELL", 21299.00, "http://image.huawei.com/13123.jpg"));
list.add(new Product(19L, "联想小新", "电脑", "联想", 6299.00, "http://image.huawei.com/13123.jpg"));
//1. 创建Request
BulkRequest bulkRequest = new BulkRequest("product");
//2. 添加数据
for (Product product : list) {
//2.1 把一个Product对象,封装到一个IndexRequest对象里
IndexRequest indexRequest = new IndexRequest().id(product.getId().toString());
indexRequest.source(JSON.toJSONString(product), XContentType.JSON);
//2.2 把一个IndexRequest对象添加到BulkRequest对象里e
bulkRequest.add(indexRequest);
}
//3. 发送请求
client.bulk(bulkRequest, RequestOptions.DEFAULT);
}
三、Java搜索文档(重点)
文档搜索的基本步骤是:
- 创建
SearchRequest对象- 准备
request.source(),也就是DSL。QueryBuilders来构建查询条件- 传入
request.source()的query()方法- 发送请求,得到结果
- 解析结果(参考JSON结果,从外到内,逐层解析)
1.快速入门
API说明
发起请求得到响应
-
SearchRequest:用于封装检索参数
searchRequest对象.source()提供了构造检索参数的一系列方法:searchRequest对象.source() .query(检索条件) //可以使用QueryBuilders构造各种不同类型的查询条件 .sort(排序) .from(起始索引).size(查询数量) .highlighter(高亮);QueryBuilders提供了一系列方法,用于构造不同类型的查询条件,常用的有:QueryBuilders.matchAllQuery():查询全部QueryBuilders.matchQuery("字段", 值):match查询条件QueryBuilders.multiMatchQuery(值, "字段1", "字段2", ...):multi_match查询条件QueryBuilders.termQuery("字段", 值):term查询条件QueryBuilders.rangeQuery("字段").gte(值1).lte(值2):range查询条件QueryBuilders.geoDistanceQuery("字段").distance(距离,长度单位):geo_distance查询条件QueryBuilders.functionScoreQuery():算分函数查询QueryBuilders.boolQuery().must(..).should(..).mustNot(..).filter(..):bool查询条件
-
client.search(SearchRequest request, RequestOptions options):发起请求进行检索
处理响应得到结果
client.search()方法返回的结果是一个JSON,结构包含:
hits:命中的结果total:总条数,其中的value是具体的总条数值max_score:所有结果中得分最高的文档的相关性算分hits:搜索结果的文档数组,其中的每个文档都是一个json对象_source:文档中的原始数据,也是json对象
因此,我们解析响应结果,就是逐层解析JSON,流程如下:
SearchHits:通过response.getHits()获取,就是JSON中的最外层的hits,代表命中的结果searchHits对象.getTotalHits().value:获取总条数信息searchHits对象.getHits():获取SearchHit数组,也就是文档数组searchHit对象.getSourceAsString():获取文档结果中的_source,即原始的json文档数据
示例
完整代码示例:
public class Demo01 {
@Autowired
private RestHighLevelClient client;
@Test
public void testMatchAll() throws IOException {
//1. 构造SearchRequest对象。构造参数:索引库名
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询信息
request.source().query(QueryBuilders.matchAllQuery());
//3. 发起请求,得到响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 处理响应,得到结果
SearchHits result = response.getHits();
// 总数量
long total = result.getTotalHits().value;
System.out.println("总数量:" + total);
// 列表
SearchHit[] hits = result.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
}
2.match查询
API说明
构造查询条件时:
QueryBuilders.matchQuery("字段", 值):match查询条件QueryBuilders.multiMatchQuery(值, "字段1", "字段2", ...):multi_match查询条件
//==========match构造查询条件==========
QueryBuilders.matchQuery("字段名", "值")
示例
@Test
public void testMatch() throws IOException {
//1. 构造SearchRequest对象。构造参数:索引库名
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询信息
// match
request.source().query(QueryBuilders.matchQuery("all", "北京酒店"));
// multi_match。字段过多的话,会影响性能,不推荐使用
//request.source().query(QueryBuilders.multiMatchQuery("北京酒店", "name", "brand"));
//3. 发起请求,得到响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 处理响应,得到结果
SearchHits result = response.getHits();
// 总数量
long total = result.getTotalHits().value;
System.out.println("总数量:" + total);
// 列表
SearchHit[] hits = result.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
3.精确查询
API说明
构造查询条件时:
QueryBuilders.termQuery("字段", 值):term查询条件QueryBuilders.rangeQuery("字段").gte(值1).lte(值2):range查询条件
//==========构造精确查询条件==========
QueryBuilders.termQuery("字段名", "值");
QueryBuilders.rangeQuery("字段名").gte(最小值).lt(最大值);
示例
@Test
public void testTerm() throws IOException {
//1. 构造SearchRequest对象。构造参数:索引库名
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询信息
// term
// request.source().query(QueryBuilders.termQuery("brand", "如家"));
// range
request.source().query(QueryBuilders.rangeQuery("price").gte(300).lte(500));
//3. 发起请求,得到响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 处理响应,得到结果
SearchHits result = response.getHits();
// 总数量
long total = result.getTotalHits().value;
System.out.println("总数量:" + total);
// 列表
SearchHit[] hits = result.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
4.布尔查询
API说明
构造查询条件时:
QueryBuilders.boolQuery().must(..).should(..).mustNot(..).filter(..):bool查询条件
//==========构造bool查询条件==========
QueryBuilders.boolQuery()
.must(主搜索条件)
.should(偏好条件)
.filter(过滤条件)
.mustNot(剔除条件);
示例代码
@Test
public void testBoolQuery() throws IOException {
//1. 构造SearchRequest对象。构造参数:索引库名
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询信息
// bool查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
//must条件
.must(QueryBuilders.matchQuery("all", "北京酒店"))
//should条件
.should(QueryBuilders.termQuery("starName", "五星"))
//must_not条件
.mustNot(QueryBuilders.rangeQuery("score").lte(40))
//filter条件
.filter(QueryBuilders.termQuery("brand", "希尔顿"));
request.source().query(boolQuery);
//3. 发起请求,得到响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 处理响应,得到结果
SearchHits result = response.getHits();
// 总数量
long total = result.getTotalHits().value;
System.out.println("总数量:" + total);
// 列表
SearchHit[] hits = result.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
5. 算分函数【了解】
@Test
public void testFunctionScore() throws IOException {
SearchRequest request = new SearchRequest("hotel");
//设置查询条件
FunctionScoreQueryBuilder scoreQueryBuilder = QueryBuilders.functionScoreQuery(
//基础查询
QueryBuilders.matchQuery("all", "北京酒店"),
//对应DSL里functions数组
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
//filter,过滤出来要重新算分的数据
QueryBuilders.termQuery("brand", "希尔顿"),
//设置算分函数,使用权重值
ScoreFunctionBuilders.weightFactorFunction(10)
)
}
);
//处分函数的加权模式:Multiply,相乘。数据的原始得分 乘 权重值。如果不设置加权模式,默认就是相乘
scoreQueryBuilder.boostMode(CombineFunction.MULTIPLY);
request.source().query(scoreQueryBuilder);
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
SearchHits result = response.getHits();
// 获取总数量
long total = result.getTotalHits().value;
System.out.println("总数量:" + total);
// 获取数据列表
SearchHit[] hits = result.getHits();
for (SearchHit hit : hits) {
//获取文档对象的原始数据
String docJson = hit.getSourceAsString();
HotelDoc doc = JSON.parseObject(docJson, HotelDoc.class);
System.out.println("查询得到的数据:" + doc);
System.out.println("匹配度得分:" + hit.getScore());
}
}
6.排序、分页
API说明
搜索结果的排序和分页是与query同级的参数,因此同样是使用request.source()来设置。
searchRequest对象.source()
.query(检索条件)
.sort(排序)
.from(起始索引).size(查询数量)
.highlighter(高亮);
//=========构造排序条件=========
// 简单排序
request.source()
.sort("排序字段", SortOrder.排序规则)
.sort("排序字段", SortOrder.排序规则);
// 距离排序
SortBuilder sortBuilder = SortBuilders
.geoDistanceSort("坐标字段名", 圆心点纬度, 圆心点经度)
.order(SortOrder.ASC)
.unit(DistanceUnit.距离单位);
request.source().sort(sortBuilder);
//=======构造分页条件===========
// 分页的from值 = (页码-1)*每页几条
request.source().from(分页的起始索引).size(分页的每页几条);
示例
@Test
public void testSortAndPage() throws IOException {
//1. 构造SearchRequest对象。构造参数:索引库名
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询信息
request.source()
.query(QueryBuilders.matchAllQuery())
//按照price升序排列
.sort(SortBuilders.fieldSort("price").order(SortOrder.ASC))
//从索引0开始,查询5条
.from(0).size(5);
//3. 发起请求,得到响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 处理响应,得到结果
SearchHits result = response.getHits();
// 总数量
long total = result.getTotalHits().value;
System.out.println("总数量:" + total);
// 列表
SearchHit[] hits = result.getHits();
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsString());
}
}
7.高亮
高亮的代码与之前代码差异较大,有两点:
- 查询的DSL:其中除了查询条件,还需要添加高亮条件,同样是与query同级。
- 结果解析:结果除了要解析_source文档数据,还要解析高亮结果
API说明
构建高亮请求
searchRequest对象.source()
.query(检索条件)
.sort(排序)
.from(起始索引).size(查询数量)
.highlighter(高亮); // 使用HighLightBuilder设置高亮信息
//======构造高亮条件============
HighlightBuilder hb = new HighlightBuilder()
.field("高亮字段名").preTags("前置标签").postTags("后置标签")
.requireFieldMatch(false);
构造高亮请求如下:
注意:高亮查询必须使用文本检索查询,并且要有搜索关键字,将来才可以对关键字高亮。
处理高亮结果
高亮的结果与查询的文档结果默认是分离的,并不在一起。
因此解析高亮的代码需要额外处理:
代码说明:
-
从结果中获取原始文档json:
hit.getSourceAsString(),这部分是非高亮的结果,json格式的字符串。还需要反序列为HotelDoc对象 -
获取高亮结果,替换掉原本的非高亮值:
-
获取高亮Map:
hit.getHighlightFields()返回值是一个Map,key是高亮字段名称,值是
HighlightField对象,代表高亮值 -
从高亮Map中,根据高亮字段名称,获取高亮字段值对象
HighlightField -
从
HighlightField中获取Fragments,并且转为字符串。这部分就是真正的高亮字符串了 -
用高亮的结果替换HotelDoc中的非高亮结果
-
示例
@Test
public void testHighLight() throws IOException {
//1. 构造SearchRequest对象。构造参数:索引库名
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询信息
request.source()
.query(QueryBuilders.matchQuery("all", "如家"))
// name中关键字高亮显示
.highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
//3. 发起请求,得到响应
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 处理响应,得到结果
SearchHits result = response.getHits();
// 总数量
long total = result.getTotalHits().value;
System.out.println("总数量:" + total);
// 列表
SearchHit[] hits = result.getHits();
for (SearchHit hit : hits) {
// 获取原始文档数据(没有高亮的)
HotelDoc doc = JSON.parseObject(hit.getSourceAsString(), HotelDoc.class);
// 获取高亮值,把doc里非高亮值替换掉
Map<String, HighlightField> highlightFieldMap = hit.getHighlightFields();
if (highlightFieldMap != null) {
HighlightField nameField = highlightFieldMap.get("name");
if (nameField != null) {
String nameHighLightValue = nameField.getFragments()[0].string();
doc.setName(nameHighLightValue);
}
}
System.out.println(doc);
}
}
8. 聚合
API语法
聚合条件与query条件同级别,因此需要使用request.source()来指定聚合条件。
聚合条件的语法:
聚合的结果也与查询结果不同,API也比较特殊。不过同样是JSON逐层解析:
示例
@SpringBootTest
@RunWith(SpringRunner.class)
public class Demo01AggTest {
@Autowired
private RestHighLevelClient esClient;
/**
* 聚合为桶
*/
@Test
public void test01() throws IOException {
SearchRequest request = new SearchRequest("hotel");
//查询文档数量为0
TermsAggregationBuilder aggregationBuilder = AggregationBuilders
//聚合类型:terms聚合,聚合名称:brandAgg
.terms("brandAgg")
//聚合字段
.field("brand")
//桶的数量
.size(20)
//聚合排序
.order(BucketOrder.count(true));
request.source()
.size(0)
.aggregation(aggregationBuilder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
//获取所有聚合结果
Aggregations aggregations = response.getAggregations();
// 获取名称为brandAgg的聚合结果。是terms聚合,要把返回值转换成Terms
Terms brandAgg = aggregations.get("brandAgg");
// 获取所有桶
List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
for (Terms.Bucket bucket : buckets) {
String key = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
System.out.println("key = " + key + ", docCount = " + docCount);
}
}
/**
* 桶内度量
*/
@Test
public void test02() throws IOException {
SearchRequest request = new SearchRequest("hotel");
TermsAggregationBuilder aggregationBuilder = AggregationBuilders
.terms("brandAgg")
.field("brand")
.size(100)
.subAggregation(AggregationBuilders.stats("statAgg").field("price"))
.order(BucketOrder.aggregation("statAgg.avg", true));
request.source()
.size(0)
.aggregation(aggregationBuilder);
SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);
Aggregations aggregations = response.getAggregations();
Terms brandAgg = aggregations.get("brandAgg");
List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
buckets.forEach(bucket->{
String key = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
Stats statAgg = bucket.getAggregations().get("statAgg");
long count = statAgg.getCount();
double sum = statAgg.getSum();
double max = statAgg.getMax();
double min = statAgg.getMin();
double avg = statAgg.getAvg();
System.out.println(String.format("key:%s, docCount:%s, sum:%s, max:%s, min:%s, avg:%s", key, docCount, sum, max, min, avg));
});
}
}