索引库操作
索引库就类似数据库表,mapping映射就类似表的结构。
我们要向es中存储数据,必须先创建"库"和"表"。
mapping映射属性
mapping是对索引库中文档的约束,常见的mapping属性包括:
-
type:字段数据类型,常见的简单类型有:
- 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
- 数值:long、integer、short、byte、double、float、
- 布尔:boolean
- 日期:date
- 对象:object
-
index:是否创建索引,默认为true
-
analyzer:使用哪种分词器
-
properties:该字段的子字段
例如下面的json文档:
json
{
"age": 21,
"weight": 52.1,
"isMarried": false,
"info": "牛马程序员",
"email": "cmr@itcast.cn",
"score": [99.1, 99.5, 98.9],
"name": {
"firstName": "某人",
"lastName": "陈"
}
}
对应的每个字段映射(mapping):
-
age:类型为 integer;参与搜索,因此需要index为true;无需分词器
-
weight:类型为float;参与搜索,因此需要index为true;无需分词器
-
isMarried:类型为boolean;参与搜索,因此需要index为true;无需分词器
-
info:类型为字符串,需要分词,因此是text;参与搜索,因此需要index为true;分词器可以用ik_smart
-
email:类型为字符串,但是不需要分词,因此是keyword;不参与搜索,因此需要index为false;无需分词器
-
score:虽然是数组,但是我们只看元素的类型,类型为float;参与搜索,因此需要index为true;无需分词器
-
name:类型为object,需要定义多个子属性
- name.firstName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
- name.lastName;类型为字符串,但是不需要分词,因此是keyword;参与搜索,因此需要index为true;无需分词器
基础索引库的CRUD
使用Kibana编写DSL的方式来演示。
创建索引库和映射
基本语法:
- 请求方式:PUT
- 请求路径:/索引库名,可以自定义
- 请求参数:mapping映射
json
# 创建名为 "heima" 的索引库
PUT /heima
{
"mappings": {
"properties": {
# 配置 "info" 字段,类型为 "text",使用 "ik_smart" 分词器,允许索引和存储
"info": {
"type": "text",
"analyzer": "ik_smart",
"index": true,
"store": true
},
# 配置 "email" 字段,类型为 "text",使用 "ik_max_word" 分词器
"email": {
"type": "text",
"analyzer": "ik_max_word"
},
# 配置 "brand" 字段,类型为 "keyword",用于存储关键字
"brand": {
"type": "keyword"
},
# 配置 "name" 字段为嵌套对象
"name": {
"properties": {
# 配置 "firstName" 子字段,类型为 "keyword"
"firstName": {
"type": "keyword"
},
# 配置 "lastName" 子字段,类型为 "keyword"
"lastName": {
"type": "keyword"
}
}
},
# 配置 "birth" 字段,类型为 "date",日期格式为 "yyyy-MM-dd"
"birth": {
"type": "date",
"format": "yyyy-MM-dd"
}
}
}
}
JAVA代码演示,在测试类中写
java
/**
* 创建索引库
* @throws Exception
*/
@Test
public void createIndexTest() throws Exception{
// 创建创建索引请求对象,指定索引名为 EsConstant.HOTEL_INDEX
CreateIndexRequest createIndexRequest = new CreateIndexRequest(EsConstant.HOTEL_INDEX);
// 指定映射的内容为 EsConstant.HOTEL_MAPPING,并设置内容类型为 JSON
createIndexRequest.source(EsConstant.HOTEL_MAPPING, XContentType.JSON);
// 执行创建索引操作
CreateIndexResponse response = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
// 获取响应中的索引和创建是否成功的标志
String index = response.index();
boolean ack = response.isAcknowledged();
// 打印索引名和创建是否成功的标志
System.out.println(index + ":" + ack);
}
查询索引库
基本语法:
- 请求方式:GET
- 请求路径:/索引库名
- 请求参数:无
格式: GET /索引库名
示例:
bash
# 查询索引库
GET /heima
java测试类代码
arduino
/**
* 查询索引库
* @throws Exception
*/
@Test
public void getIndexTest()throws Exception{
// 创建一个GetIndexRequest对象,指定要获取映射信息的索引名称为EsConstant.HOTEL_INDEX
GetIndexRequest getIndexRequest = new GetIndexRequest(EsConstant.HOTEL_INDEX);
// 使用client.indices().get方法发送获取索引映射的请求,并将返回结果保存在GetIndexResponse对象中
GetIndexResponse response = client.indices().get(getIndexRequest, RequestOptions.DEFAULT);
// 通过GetIndexResponse对象的getMappings()方法获取到索引的映射信息,并将其存储在一个Map对象中
Map<String, MappingMetadata> map = response.getMappings();
// 打印输出映射信息的map对象
System.out.println(map);
}
修改索引库
倒排索引结构虽然不复杂,但是一旦数据结构改变(比如改变了分词器),就需要重新创建倒排索引,这简直是灾难。因此索引库一旦创建,无法修改mapping。
虽然无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中,因为不会对倒排索引产生影响。
语法说明:
bash
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}
示例:
json
# 修改名为 "heima" 的索引的映射
PUT /heima/_mapping
{
"properties": {
# 配置 "imageURL" 字段,将其数据类型修改为 "keyword",并允许存储
"imageURL": {
"type": "keyword",
"store": true
}
}
}
删除索引库
语法:
- 请求方式:DELETE
- 请求路径:/索引库名
- 请求参数:无
格式:
sql
DELETE /索引库名
在kibana中测试:
bash
# 删除索引
DELETE /heima
Java测试类代码
java
/**
* 删除索引
* @throws Exception
*/
@Test
public void deleteTest()throws Exception{
// 创建一个DeleteIndexRequest对象,指定要删除的索引名称为EsConstant.HOTEL_INDEX
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(EsConstant.HOTEL_INDEX);
// 使用client.indices().delete方法发送删除索引的请求,并将返回结果保存在AcknowledgedResponse对象中
AcknowledgedResponse response = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
// 通过AcknowledgedResponse对象的isAcknowledged()方法获取删除操作是否被确认
boolean ack = response.isAcknowledged();
// 打印输出删除操作是否被确认的结果
System.out.println(ack);
}
以上测试类的部分代码合集:
java
package cn.itcast.hotel;
import cn.itcast.hotel.mapper.EsConstant;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.cluster.metadata.MappingMetadata;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.Map;
@SpringBootTest
class HotelDemoApplicationTests {
@Autowired
RestHighLevelClient client;
/**
* 删除索引
* @throws Exception
*/
@Test
public void deleteTest()throws Exception{
// 创建一个DeleteIndexRequest对象,指定要删除的索引名称为EsConstant.HOTEL_INDEX
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(EsConstant.HOTEL_INDEX);
// 使用client.indices().delete方法发送删除索引的请求,并将返回结果保存在AcknowledgedResponse对象中
AcknowledgedResponse response = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
// 通过AcknowledgedResponse对象的isAcknowledged()方法获取删除操作是否被确认
boolean ack = response.isAcknowledged();
// 打印输出删除操作是否被确认的结果
System.out.println(ack);
}
/**
* 查询索引
* @throws Exception
*/
@Test
public void getIndexTest()throws Exception{
// 创建一个GetIndexRequest对象,指定要获取映射信息的索引名称为EsConstant.HOTEL_INDEX
GetIndexRequest getIndexRequest = new GetIndexRequest(EsConstant.HOTEL_INDEX);
// 使用client.indices().get方法发送获取索引映射的请求,并将返回结果保存在GetIndexResponse对象中
GetIndexResponse response = client.indices().get(getIndexRequest, RequestOptions.DEFAULT);
// 通过GetIndexResponse对象的getMappings()方法获取到索引的映射信息,并将其存储在一个Map对象中
Map<String, MappingMetadata> map = response.getMappings();
// 打印输出映射信息的map对象
System.out.println(map);
}
/**
* 创建索引库
* @throws Exception
*/
@Test
public void createIndexTest() throws Exception{
// 创建创建索引请求对象,指定索引名为 EsConstant.HOTEL_INDEX
CreateIndexRequest createIndexRequest = new CreateIndexRequest(EsConstant.HOTEL_INDEX);
// 指定映射的内容为 EsConstant.HOTEL_MAPPING,并设置内容类型为 JSON
createIndexRequest.source(EsConstant.HOTEL_MAPPING, XContentType.JSON);
// 执行创建索引操作
CreateIndexResponse response = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
// 获取响应中的索引和创建是否成功的标志
String index = response.index();
boolean ack = response.isAcknowledged();
// 打印索引名和创建是否成功的标志
System.out.println(index + ":" + ack);
}
@Test
void contextLoads() throws IOException {
client.close();
}
}
关于文档的操作
先准备实体类 hotelDoc(演示作用),实体类、数据库自行准备、数据太多
ini
package cn.itcast.hotel.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class HotelDoc {
private Long id;
private String name;
private String address;
private Integer price;
private Integer score;
private String brand;
private String city;
private String starName;
private String business;
private String location;
private String pic;
public HotelDoc(Hotel hotel) {
this.id = hotel.getId();
this.name = hotel.getName();
this.address = hotel.getAddress();
this.price = hotel.getPrice();
this.score = hotel.getScore();
this.brand = hotel.getBrand();
this.city = hotel.getCity();
this.starName = hotel.getStarName();
this.business = hotel.getBusiness();
this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
this.pic = hotel.getPic();
}
}
新增文档
语法:
bash
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
// ...
}
示例:
json
# 向名为 "heima" 的索引中增加一个文档,文档 ID 为 "1001"
POST /heima/_doc/1001
{
# 配置 "birth" 字段的值为 "2003-08-16",日期格式为 "yyyy-MM-dd"
"birth": "2003-08-16",
# 配置 "brand" 字段的值为 "白马"
"brand": "白马",
# 配置 "email" 字段的值为 "123456789@163.com"
"email": "123456789@163.com",
# 配置 "name" 字段为嵌套对象,包括 "firstName" 和 "lastName" 两个子字段
"name": {
"firstName": "杭州",
"lastName": "程序员"
}
}
Java测试类代码
java
/**
* 增加文档
* @throws Exception
*/
@Test
public void addTest() throws Exception {
// 通过酒店服务获取酒店信息
Hotel hotel = hotelService.getById(36934L);
// 创建HotelDoc对象,用于将酒店信息转换为文档
HotelDoc hotelDoc = new HotelDoc(hotel);
// 创建IndexRequest对象,指定索引名称和文档id,并设置文档来源为 hotelDoc
IndexRequest indexRequest = new IndexRequest(EsConstant.HOTEL_INDEX)
.id(hotelDoc.getId().toString())
.source(JSONObject.toJSONString(hotelDoc), XContentType.JSON);
// 使用客户端执行索引请求
IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT);
// 获取响应中的文档id
String id = response.getId();
System.out.println("id:" + id);
}
查询文档
根据rest风格,新增是post,查询应该是get,不过查询一般都需要条件,这里我们把文档id带上。
语法:
bash
GET /{索引库名称}/_doc/{id}
通过kibana查看数据:
bash
# 根据ID查询文档
GET /heima/_doc/1001
Java测试类代码
java
/**
* 根据ID查询文档
* @throws Exception
*/
@Test
public void getDocTest() throws Exception{
// 创建GetRequest对象,指定索引名称和文档id
GetRequest getRequest = new GetRequest(EsConstant.HOTEL_INDEX, "36934");
// 使用客户端执行获取请求
GetResponse response = client.get(getRequest, RequestOptions.DEFAULT);
// 从响应中获取文档的JSON字符串表示
String json = response.getSourceAsString();
// 将JSON字符串转换为HotelDoc对象
HotelDoc hotelDoc = JSONObject.parseObject(json, HotelDoc.class);
// 打印获取到的酒店文档
System.out.println(hotelDoc);
}
删除文档
删除使用DELETE请求,同样,需要根据id进行删除:
语法:
bash
DELETE /{索引库名}/_doc/id值
示例:
bash
# 根据id删除数据
DELETE /heima/_doc/1001
Java测试类代码
java
/**
* 根据ID删除
* @throws Exception
*/
@Test
public void deleteDocTest()throws Exception{
// 创建DeleteRequest对象,指定索引名称和文档id
DeleteRequest deleteRequest = new DeleteRequest(EsConstant.HOTEL_INDEX, "36934");
// 使用客户端执行删除请求
DeleteResponse response = client.delete(deleteRequest, RequestOptions.DEFAULT);
// 获取响应中被删除文档的id
String id = response.getId();
// 打印已删除文档的id
System.out.println("已经删除id为:" + id + "的文档");
}
修改文档
修改有两种方式:
- 全量修改:直接覆盖原来的文档
- 增量修改:修改文档中的部分字段
3.4.1.全量修改
全量修改是覆盖原来的文档,其本质是:
- 根据指定的id删除文档
- 新增一个相同id的文档
注意:如果根据id删除时,id不存在,第二步的新增也会执行,也就从修改变成了新增操作了。
语法:
bash
PUT /{索引库名}/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
// ... 略
}
示例:
json
# 对名为 "heima" 索引中的文档进行全量修改,文档 ID 为 "1001"
PUT /heima/_doc/1001
{
# 修改 "birth" 字段的值为 "2003-08-16",日期格式为 "yyyy-MM-dd"
"birth": "2003-08-16",
# 修改 "brand" 字段的值为 "生椰拿铁"
"brand": "生椰拿铁",
# 修改 "email" 字段的值为 "123456789@163.com"
"email": "123456789@163.com",
# 修改 "name" 字段为嵌套对象,包括 "firstName" 和 "lastName" 两个子字段
"name": {
"firstName": "拿铁",
"lastName": "生椰"
}
}
增量修改
增量修改是只修改指定id匹配的文档中的部分字段。
语法:
bash
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}
示例:
bash
# 增量修改
POST /heima/_update/1
{
"doc": {
"email": "chenbolong@itcast.cn"
}
}
Java测试类代码
java
/**
* 根据id修改文档
* @throws Exception
*/
@Test
public void updateDocTest()throws Exception{
// 通过酒店服务获取酒店信息
Hotel hotel = hotelService.getById(36934);
// 创建HotelDoc对象,用于将酒店信息转换为文档
HotelDoc hotelDoc = new HotelDoc(hotel);
// 修改酒店文档的字段值
hotelDoc.setName("宇宙大酒店");
hotelDoc.setPrice(21980);
hotelDoc.setBusiness("下沙四号大街");
// 创建UpdateRequest对象,指定索引名称和文档id,并设置更新内容为 hotelDoc
UpdateRequest updateRequest = new UpdateRequest(EsConstant.HOTEL_INDEX, "36934")
.doc(JSONObject.toJSONString(hotelDoc), XContentType.JSON);
// 使用客户端执行更新请求
UpdateResponse response = client.update(updateRequest, RequestOptions.DEFAULT);
// 获取响应中的更新状态
int status = response.status().getStatus();
// 打印更新状态
System.out.println("status:" + status);
}
总结
文档操作有哪些?
-
创建文档:POST /{索引库名}/_doc/文档id { json文档 }
-
查询文档:GET /{索引库名}/_doc/文档id
-
删除文档:DELETE /{索引库名}/_doc/文档id
-
修改文档:
- 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }
- 增量修改:POST /{索引库名}/_update/文档id { "doc": {字段}}
扩展>>>将本地数据库添加到ES
JAVA代码
scss
/**
* 从数据库批量添加到ES
* @throws Exception
*/
@Test
public void bulkRequestTest() throws Exception{
// 从数据库中获取酒店数据列表
List<Hotel> hotelLists = hotelService.list();
// 将酒店数据列表转换为酒店文档数据列表
List<HotelDoc> hotelDocList = hotelLists.stream().map(HotelDoc::new).collect(Collectors.toList());
// 创建批量请求对象
BulkRequest bulkRequest = new BulkRequest();
// 遍历酒店文档数据列表,将每个文档添加到批量请求中
hotelDocList.forEach(item -> {
bulkRequest.add(
new IndexRequest(EsConstant.HOTEL_INDEX) // 指定索引
.id(item.getId().toString()) // 指定文档 ID
.source(JSONObject.toJSONString(item), XContentType.JSON) // 设置文档内容
);
});
// 执行批量请求,将酒店文档数据批量索引到 Elasticsearch 中
BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
// 获取批量请求的执行状态
int status = bulk.status().getStatus();
System.out.println("status:" + status);
}
文档操作Java代码整合
java
package cn.itcast.hotel;
import cn.itcast.hotel.mapper.EsConstant;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.service.impl.HotelService;
import com.alibaba.fastjson.JSONObject;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.xcontent.XContentType;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@SpringBootTest
public class HotelDocTest {
@Autowired
RestHighLevelClient client;
@Autowired
private HotelService hotelService;
/**
* 从数据库批量添加到ES
* @throws Exception
*/
@Test
public void bulkRequestTest() throws Exception{
// 从数据库中获取酒店数据列表
List<Hotel> hotelLists = hotelService.list();
// 将酒店数据列表转换为酒店文档数据列表
List<HotelDoc> hotelDocList = hotelLists.stream().map(HotelDoc::new).collect(Collectors.toList());
// 创建批量请求对象
BulkRequest bulkRequest = new BulkRequest();
// 遍历酒店文档数据列表,将每个文档添加到批量请求中
hotelDocList.forEach(item -> {
bulkRequest.add(
new IndexRequest(EsConstant.HOTEL_INDEX) // 指定索引
.id(item.getId().toString()) // 指定文档 ID
.source(JSONObject.toJSONString(item), XContentType.JSON) // 设置文档内容
);
});
// 执行批量请求,将酒店文档数据批量索引到 Elasticsearch 中
BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
// 获取批量请求的执行状态
int status = bulk.status().getStatus();
System.out.println("status:" + status);
}
/**
* 根据ID删除
* @throws Exception
*/
@Test
public void deleteDocTest()throws Exception{
// 创建DeleteRequest对象,指定索引名称和文档id
DeleteRequest deleteRequest = new DeleteRequest(EsConstant.HOTEL_INDEX, "36934");
// 使用客户端执行删除请求
DeleteResponse response = client.delete(deleteRequest, RequestOptions.DEFAULT);
// 获取响应中被删除文档的id
String id = response.getId();
// 打印已删除文档的id
System.out.println("已经删除id为:" + id + "的文档");
}
/**
* 根据id修改文档
* @throws Exception
*/
@Test
public void updateDocTest()throws Exception{
// 通过酒店服务获取酒店信息
Hotel hotel = hotelService.getById(36934);
// 创建HotelDoc对象,用于将酒店信息转换为文档
HotelDoc hotelDoc = new HotelDoc(hotel);
// 修改酒店文档的字段值
hotelDoc.setName("宇宙大酒店");
hotelDoc.setPrice(21980);
hotelDoc.setBusiness("下沙四号大街");
// 创建UpdateRequest对象,指定索引名称和文档id,并设置更新内容为 hotelDoc
UpdateRequest updateRequest = new UpdateRequest(EsConstant.HOTEL_INDEX, "36934")
.doc(JSONObject.toJSONString(hotelDoc), XContentType.JSON);
// 使用客户端执行更新请求
UpdateResponse response = client.update(updateRequest, RequestOptions.DEFAULT);
// 获取响应中的更新状态
int status = response.status().getStatus();
// 打印更新状态
System.out.println("status:" + status);
}
/**
* 根据ID查询文档
* @throws Exception
*/
@Test
public void getDocTest() throws Exception{
// 创建GetRequest对象,指定索引名称和文档id
GetRequest getRequest = new GetRequest(EsConstant.HOTEL_INDEX, "36934");
// 使用客户端执行获取请求
GetResponse response = client.get(getRequest, RequestOptions.DEFAULT);
// 从响应中获取文档的JSON字符串表示
String json = response.getSourceAsString();
// 将JSON字符串转换为HotelDoc对象
HotelDoc hotelDoc = JSONObject.parseObject(json, HotelDoc.class);
// 打印获取到的酒店文档
System.out.println(hotelDoc);
}
/**
* 增加文档
* @throws Exception
*/
@Test
public void addTest() throws Exception {
// 通过酒店服务获取酒店信息
Hotel hotel = hotelService.getById(36934L);
// 创建HotelDoc对象,用于将酒店信息转换为文档
HotelDoc hotelDoc = new HotelDoc(hotel);
// 创建IndexRequest对象,指定索引名称和文档id,并设置文档来源为 hotelDoc
IndexRequest indexRequest = new IndexRequest(EsConstant.HOTEL_INDEX)
.id(hotelDoc.getId().toString())
.source(JSONObject.toJSONString(hotelDoc), XContentType.JSON);
// 使用客户端执行索引请求
IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT);
// 获取响应中的文档id
String id = response.getId();
System.out.println("id:" + id);
}
}
DSL查询文档
elasticsearch的查询依然是基于JSON风格的DSL来实现的。
1.1.DSL查询分类
Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括:
-
查询所有:查询出所有数据,一般测试用。例如:match_all
-
全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
- match_query
- multi_match_query
-
精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
- ids
- range
- term
-
地理(geo)查询:根据经纬度查询。例如:
- geo_distance
- geo_bounding_box
-
复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
- bool
- function_score
查询的语法基本一致:
bash
GET /indexName/_search
{
"query": {
"查询类型": {
"查询条件": "条件值"
}
}
}
我们以查询所有为例,其中:
- 查询类型为match_all
- 没有查询条件
bash
// 查询所有
GET /indexName/_search
{
"query": {
"match_all": {
}
}
}
其它查询无非就是查询类型 、查询条件的变化。
全文检索查询
使用场景
全文检索查询的基本流程如下:
- 对用户搜索的内容做分词,得到词条
- 根据词条去倒排索引库中匹配,得到文档id
- 根据文档id找到文档,返回给用户
比较常用的场景包括:
- 商城的输入框搜索
- 百度输入框搜索
因为是拿着词条去匹配,因此参与搜索的字段也必须是可分词的text类型的字段。
基本语法
常见的全文检索查询包括:
- match查询:单字段查询
- multi_match查询:多字段查询,任意一个字段符合条件就算符合查询条件
match查询语法如下:
bash
GET /indexName/_search
{
"query": {
"match": {
"FIELD": "TEXT"
}
}
}
mulit_match语法如下:
bash
GET /indexName/_search
{
"query": {
"multi_match": {
"query": "TEXT",
"fields": ["FIELD1", " FIELD12"]
}
}
}
match示例:
sql
# 执行 match 查询,搜索名为 "hotel" 的索引中匹配 "all" 字段包含 "7天酒店" 的文档
GET /hotel/_search
{
"query": {
"match": {
"all": "7天酒店"
}
}
}
Java测试类代码
java
/**
* 全文检索,分词后搜索
*
* @throws Exception
*/
@Test
public void matchTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为匹配指定字段"all"中包含"7天酒店"的文档
searchRequest.source()
.query(QueryBuilders.matchQuery("all", "7天酒店"));
// 执行搜索请求并获取搜索响应
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(response);
}
mulit_match示例:
sql
# 执行 multi_match 查询,搜索名为 "hotel" 的索引中匹配包含 "北京" 的文档,
# 匹配字段包括 "name"、"city" 和 "business"
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "北京", # 查询的文本是 "北京"
"fields": ["name", "city", "business"] # 匹配的字段包括 "name"、"city" 和 "business"
}
}
}
Java测试类代码
java
/**
* nulti_match
*
* @throws Exception
*/
@Test
public void multiMatchTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为多字段匹配,搜索关键词为"北京",匹配字段包括"name"、"city"和"business"
searchRequest.source()
.query(QueryBuilders.multiMatchQuery("北京", "name", "city", "business"));
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(search);
}
我们将brand、name、business值都利用copy_to复制到了all字段中。因此你根据三个字段搜索,和根据all字段搜索效果当然一样了。
但是,搜索字段越多,对查询性能影响越大,因此建议采用copy_to,然后单字段查询的方式。
match和multi_match的区别是什么?
- match:根据一个字段查询
- multi_match:根据多个字段查询,参与查询字段越多,查询性能越差
精准查询
精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有:
- term:根据词条精确值查询
- range:根据值的范围查询
term查询
因为精确查询的字段搜是不分词的字段,因此查询的条件也必须是不分词的词条。查询时,用户输入的内容跟自动值完全匹配时才认为符合条件。如果用户输入的内容过多,反而搜索不到数据。
语法说明:
bash
// term查询
GET /indexName/_search
{
"query": {
"term": {
"FIELD": {
"value": "VALUE"
}
}
}
}
示例:
当我搜索的是精确词条时,能正确查询出结果:
sql
# 执行 term 精确查询,搜索名为 "hotel" 的索引中匹配 "brand" 字段值为 "如家" 的文档
GET /hotel/_search
{
"query": {
"term": {
"brand": { # 要匹配的字段是 "brand"
"value": "如家" # 要精确匹配的值是 "如家"
}
}
}
}
Java测试类代码
scss
/**
* term 精准查询
*
* @throws Exception
*/
@Test
public void termTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为精确匹配,匹配字段"brand"的值为"如家"
searchRequest.source()
.query(QueryBuilders.termQuery("brand", "如家"));
// 执行搜索请求并获取搜索响应
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取搜索结果中的总命中数
long value = response.getHits().getTotalHits().value;
System.out.println("total:" + value);
// 获取搜索结果中的命中文档数组
SearchHit[] hits = response.getHits().getHits();
// 遍历命中文档数组并输出每个文档的 JSON 内容
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
System.out.println(json);
}
}
range查询
范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤。
基本语法:
json
// range查询
GET /indexName/_search
{
"query": {
"range": {
"FIELD": {
"gte": 10, // 这里的gte代表大于等于,gt则代表大于
"lte": 20 // lte代表小于等于,lt则代表小于
}
}
}
}
示例:
bash
# 使用 Elasticsearch 的 range 查询,进行精确的范围查询
# 查询名为 "hotel" 的索引中,"price" 字段值在 [400, 500) 的文档
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 400, # 大于等于 400
"lt": 500 # 小于 500
}
}
}
}
Java测试类代码
scss
/**
* range 查询
*
* @throws Exception
*/
@Test
public void rangeTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为范围查询,匹配字段"price"在大于等于400且小于500的文档
searchRequest.source()
.query(QueryBuilders.rangeQuery("price").gte(400).lt(500));
// 执行搜索请求并获取搜索响应
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(response);
}
精确查询常见两种
- term查询:根据词条精确匹配,一般搜索keyword类型、数值类型、布尔类型、日期类型字段
- range查询:根据数值范围查询,可以是数值、日期的范围
地理坐标查询
所谓的地理坐标查询,其实就是根据经纬度查询,官方文档:www.elastic.co/guide/en/el...
常见的使用场景包括:
- 携程:搜索我附近的酒店
- 滴滴:搜索我附近的出租车
- 微信:搜索我附近的人
- 滴滴:搜索我附近的出租车
- 微信:搜索我附近的人
附近查询
附近查询,也叫做距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档。
换句话来说,在地图上找一个点作为圆心,以指定距离为半径,画一个圆,落在圆内的坐标都算符合条件
语法说明:
json
// geo_distance 查询
GET /indexName/_search
{
"query": {
"geo_distance": {
"distance": "15km", // 半径
"FIELD": "31.21,121.5" // 圆心
}
}
}
示例:
我们先搜索陆家嘴附近5km的酒店:
注意:纬度在前,经度在后
bash
# 搜索陆家嘴附近5km的酒店
# 121.509106,31.242201
#
GET /hotel/_search
{
"query": {
"geo_distance":{
"location":"31.242201,121.509106",
"distance":"5km"
}
}
}
Java测试类代码
scss
/**
* 根据经纬度查询
*
* @throws Exception
*/
@Test
public void geoDistanceTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为地理距离查询,在字段"location"附近半径5公里内的文档
searchRequest.source()
.query(QueryBuilders
.geoDistanceQuery("location") // 指定地理字段
.point(31.240001, 121.509106) // 圆心坐标
.distance("5", DistanceUnit.KILOMETERS)); // 距离和单位
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(search);
}
复合查询
复合(compound)查询:复合查询可以将其它简单查询组合起来,实现更复杂的搜索逻辑。常见的有两种:
- fuction score:算分函数查询,可以控制文档相关性算分,控制文档排名
- bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索
示例:
bash
# 搜索名字为 如家 的酒店 价格不高于 400 坐标在31.21,121.5范围10km内的酒店
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"all": "如家"
}
}
],
"must_not": [
{
"range": {
"price": {
"gt": 400
}
}
}
],
"filter": [
{
"geo_distance": {
"location": "31.21,121.5",
"distance": "10km"
}
}
]
}
}
}
Java测试类代码
scss
/**
* bool
* 复合多条件查询
* !!!!必须掌握!!!!
* @throws Exception
*/
@Test
public void boolTest()throws Exception{
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 构建布尔查询,包括must(必须匹配)、mustNot(不能匹配)和filter(过滤器)子句
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 添加must子句:匹配字段"all"包含"如家"的文档
boolQueryBuilder.must(QueryBuilders.matchQuery("all", "如家"));
// 添加mustNot子句:排除价格大于400的文档
boolQueryBuilder.mustNot(QueryBuilders.rangeQuery("price").gt(400));
// 添加filter子句:筛选距离坐标点(31.21,121.5)不超过10公里的文档
boolQueryBuilder.filter(QueryBuilders.geoDistanceQuery("location")
.point(31.21, 121.5)
.distance(10, DistanceUnit.KILOMETERS));
// 设置搜索请求的查询条件为布尔查询
searchRequest.source()
.query(boolQueryBuilder);
// 执行搜索请求并获取搜索响应
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(response);
}
算分函数查询
简单理解网上的广告,谁给的钱多,就推送得越多
示例:
bash
# 给品牌名为 如家 的酒店增加权重积分*10
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"term": {
"city": {
"value": "深圳"
}
}
},
"functions": [
{
"filter": {
"term": {
"branf": "如家"
}
},
"weight": 10
}
],
"boost_mode": "multiply"
}
}
}
Java测试类代码
java
/**
* 算分查询
* @throws Exception
*/
@Test
public void functionScoreTest() throws Exception {
// 创建一个用于搜索的请求对象,指定索引为 EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 创建 FunctionScoreQueryBuilder,它用于结合不同的查询条件并应用自定义的评分函数
FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(
QueryBuilders.termQuery("city", "深圳"), // 查询条件:城市为深圳
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
QueryBuilders.termQuery("brand", "如家"), // 酒店品牌为如家
ScoreFunctionBuilders.weightFactorFunction(10) // 自定义评分函数,权重为10
)
}
).boostMode(CombineFunction.MULTIPLY); // 设置评分的组合方式为 MULTIPLY(相乘)
// 配置搜索请求的内容,将 FunctionScoreQueryBuilder 作为查询条件
searchRequest.source()
.query(functionScoreQueryBuilder);
// 执行搜索请求
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 打印搜索结果
printResult(response);
}
function score query定义的三要素
- 过滤条件:哪些文档要加分
- 算分函数:如何计算function score
- 加权方式:function score 与 query score如何运算
排序
elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
普通字段排序
keyword、数值、日期类型排序的语法基本一致。
语法:
sql
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"FIELD": "desc" // 排序字段、排序方式ASC、DESC
}
]
}
排序条件是一个数组,也就是可以写多个排序条件。按照声明的顺序,当第一个条件相等时,再按照第二个条件排序,以此类推
示例:
bash
#sort 普通字段排序
GET /hotel/_search
{
"query": {
"geo_distance":{
"location": "31.242201,121.509106",
"distance":"15km"
}
},
"sort": [
{
"score": {
"order": "desc"
}
},
{
"price": {
"order": "asc"
}
}
]
}
Java测试类代码
scss
/**
* 条件排序 按照积分高 费用低排序
* @throws Exception
*/
@Test
public void sorcePriceTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为地理距离查询,在字段"location"附近半径5公里内的文档
searchRequest.source()
.query(QueryBuilders
.geoDistanceQuery("location") // 指定地理字段
.point(31.240001, 121.509106) // 圆心坐标
.distance("5", DistanceUnit.KILOMETERS))// 距离和单位
.sort("score", SortOrder.DESC) // 按照积分从高到低排序
.sort("price",SortOrder.ASC); // 按照价格从低到高排序
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(search);
}
地理坐标排序
地理坐标排序略有不同。
语法说明:
json
GET /indexName/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance" : {
"FIELD" : "纬度,经度", // 文档中geo_point类型的字段名、目标坐标点
"order" : "asc", // 排序方式
"unit" : "km" // 排序的距离单位
}
}
]
}
这个查询的含义是:
- 指定一个坐标,作为目标点
- 计算每一个文档中,指定字段(必须是geo_point类型)的坐标 到目标点的距离是多少
- 根据距离排序
示例:
需求描述:实现对酒店数据按照到你的位置坐标的距离升序排序
提示:获取你的位置的经纬度的方式:lbs.amap.com/demo/jsapi-...
假设我的位置是:31.034661,121.612282,寻找我周围距离最近的酒店。
示例:
bash
# 执行 Elasticsearch 搜索请求,根据指定地理坐标附近的文档和字段排序
GET /hotel/_search
{
"query": {
"geo_distance": {
"location": "31.242201,121.509106", # 指定中心地理坐标
"distance": "15km" # 搜索半径为 15 公里
}
},
"sort": [
{
"score": { # 根据 "score" 字段进行排序
"order": "desc" # 按降序排序
}
},
{
"price": { # 根据 "price" 字段进行排序
"order": "asc" # 按升序排序
}
}
]
}
Java测试类代码
scss
/**
* 按照坐标距离 查询附近酒店 (升序)
* @throws Exception
*/
@Test
public void geoDistanceSortTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为匹配所有文档
searchRequest.source()
.query(QueryBuilders.matchAllQuery())
// 添加地理距离排序:按照字段"location"与坐标点(31.034661, 121.612282)的距离升序排序
.sort(
SortBuilders.geoDistanceSort("location", 31.034661, 121.612282)
.order(SortOrder.ASC) // 指定排序顺序为升序
.unit(DistanceUnit.KILOMETERS) // 距离单位为千米
);
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(search);
}
分页
elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了。elasticsearch中通过修改from、size参数来控制要返回的分页结果:
- from:从第几个文档开始
- size:总共查询几个文档
类似于mysql中的limit ?, ?
基本的分页
分页的基本语法如下:
json
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 0, // 分页开始的位置,默认为0
"size": 10, // 期望获取的文档总数
"sort": [
{"price": "asc"}
]
}
深度分页问题
现在,我要查询990~1000的数据,查询逻辑要这么写:
json
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 990, // 分页开始的位置,默认为0
"size": 10, // 期望获取的文档总数
"sort": [
{"price": "asc"}
]
}
这里是查询990开始的数据,也就是 第990~第1000条 数据。
不过,elasticsearch内部分页时,必须先查询 0~1000条,然后截取其中的990 ~ 1000的这10条:
查询TOP1000,如果es是单点模式,这并无太大影响。
但是elasticsearch将来一定是集群,例如我集群有5个节点,我要查询TOP1000的数据,并不是每个节点查询200条就可以了。
因为节点A的TOP200,在另一个节点可能排到10000名以外了。
因此要想获取整个集群的TOP1000,必须先查询出每个节点的TOP1000,汇总结果后,重新排名,重新截取TOP1000。
那如果我要查询9900~10000的数据呢?是不是要先查询TOP10000呢?那每个节点都要查询10000条?汇总到内存中?
当查询分页深度较大时,汇总数据过多,对内存和CPU会产生非常大的压力,因此elasticsearch会禁止from+ size 超过10000的请求。
针对深度分页,ES提供了两种解决方案,官方文档:
- search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
- scroll:原理将排序后的文档id形成快照,保存在内存。官方已经不推荐使用。
Java测试类代码
scss
/**
* 距离升序 分页
* @throws Exception
*/
@Test
public void geoDistanceSortPageTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为匹配所有文档
searchRequest.source()
.query(QueryBuilders.matchAllQuery())
// 添加地理距离排序:按照字段"location"与坐标点(31.034661, 121.612282)的距离升序排序
.sort(
SortBuilders.geoDistanceSort("location", 31.034661, 121.612282)
.order(SortOrder.ASC) // 指定排序顺序为升序
.unit(DistanceUnit.KILOMETERS) // 距离单位为千米
)
.from(20).size(20);
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(search);
}
小结
分页查询的常见实现方案以及优缺点:
-
from + size
:- 优点:支持随机翻页
- 缺点:深度分页问题,默认查询上限(from + size)是10000
- 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
-
after search
:- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:只能向后逐页查询,不支持随机翻页
- 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
-
scroll
:- 优点:没有查询上限(单次查询的size不超过10000)
- 缺点:会有额外内存消耗,并且搜索结果是非实时的
- 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案。
高亮
高亮原理
什么是高亮显示呢?
我们在百度,京东搜索时,关键字会变成红色,比较醒目,这叫高亮显示:
高亮显示的实现分为两步:
- 1)给文档中的所有关键字都添加一个标签,例如
<em>
标签 - 2)页面给
<em>
标签编写CSS样式
2.3.2.实现高亮
高亮的语法:
json
GET /hotel/_search
{
"query": {
"match": {
"FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
}
},
"highlight": {
"fields": { // 指定要高亮的字段
"FIELD": {
"pre_tags": "<em>", // 用来标记高亮字段的前置标签
"post_tags": "</em>" // 用来标记高亮字段的后置标签
}
}
}
}
注意:
- 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
- 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
- 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false
示例:
sql
# 执行 Elasticsearch 搜索请求,使用 match 查询匹配 "all" 字段,同时对 "name" 字段进行高亮显示
GET /hotel/_search
{
"query": {
"match": {
"all": "如家" # 使用 match 查询在 "all" 字段中匹配关键词 "如家"
}
},
"highlight": {
"fields": {
"name": { # 配置高亮显示的字段为 "name"
"require_field_match": "false", # 允许跨字段高亮
"pre_tags": "<font color='red'>", # 高亮起始标签
"post_tags": "</font>" # 高亮结束标签
}
}
}
}
Java测试类代码
scss
/**
* 实现搜索文本关键字高亮
* @throws Exception
*/
@Test
public void highlightTest() throws Exception {
// 创建一个用于搜索的请求对象,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 配置搜索请求的内容
searchRequest.source()
.query(QueryBuilders.matchQuery("all","如家")) // 设置查询条件,匹配字段"all"中包含"如家"的文档
.highlighter(
new HighlightBuilder()
.field("name").requireFieldMatch(false) // 配置高亮显示,字段名为"name",不要求精确匹配
.preTags("<font color = 'red'>") // 高亮起始标签
.postTags("</font>") // 高亮结束标签
)
.from(20).size(20); // 设置分页参数,从第20条开始,每页显示20条结果
// 执行搜索请求
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取搜索结果中的总文档数
long total = response.getHits().getTotalHits().value;
System.out.println("总数为:" + total);
// 获取搜索结果中的文档
SearchHit[] hits = response.getHits().getHits();
// 遍历搜索结果文档
for (SearchHit hit : hits) {
// 获取文档中"name"字段的高亮内容
HighlightField highlightField = hit.getHighlightFields().get("name");
String highlightContent = highlightField.getFragments()[0].toString();
// 控制台输出会高亮的文本
System.out.println(highlightField);
}
}
整合以上关于文档操作得代码
scss
package cn.itcast.hotel;
import cn.itcast.hotel.mapper.EsConstant;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class HotelDocSearchTest {
@Autowired
RestHighLevelClient client;
/**
* 实现搜索文本关键字高亮
* @throws Exception
*/
@Test
public void highlightTest() throws Exception {
// 创建一个用于搜索的请求对象,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 配置搜索请求的内容
searchRequest.source()
.query(QueryBuilders.matchQuery("all","如家")) // 设置查询条件,匹配字段"all"中包含"如家"的文档
.highlighter(
new HighlightBuilder()
.field("name").requireFieldMatch(false) // 配置高亮显示,字段名为"name",不要求精确匹配
.preTags("<font color = 'red'>") // 高亮起始标签
.postTags("</font>") // 高亮结束标签
)
.from(20).size(20); // 设置分页参数,从第20条开始,每页显示20条结果
// 执行搜索请求
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取搜索结果中的总文档数
long total = response.getHits().getTotalHits().value;
System.out.println("总数为:" + total);
// 获取搜索结果中的文档
SearchHit[] hits = response.getHits().getHits();
// 遍历搜索结果文档
for (SearchHit hit : hits) {
// 获取文档中"name"字段的高亮内容
HighlightField highlightField = hit.getHighlightFields().get("name");
String highlightContent = highlightField.getFragments()[0].toString();
// 控制台输出会高亮的文本
System.out.println(highlightField);
}
}
/**
* 距离升序 分页
* @throws Exception
*/
@Test
public void geoDistanceSortPageTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为匹配所有文档
searchRequest.source()
.query(QueryBuilders.matchAllQuery())
// 添加地理距离排序:按照字段"location"与坐标点(31.034661, 121.612282)的距离升序排序
.sort(
SortBuilders.geoDistanceSort("location", 31.034661, 121.612282)
.order(SortOrder.ASC) // 指定排序顺序为升序
.unit(DistanceUnit.KILOMETERS) // 距离单位为千米
)
.from(20).size(20);
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(search);
}
/**
* 按照坐标距离 查询附近酒店 (升序)
* @throws Exception
*/
@Test
public void geoDistanceSortTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为匹配所有文档
searchRequest.source()
.query(QueryBuilders.matchAllQuery())
// 添加地理距离排序:按照字段"location"与坐标点(31.034661, 121.612282)的距离升序排序
.sort(
SortBuilders.geoDistanceSort("location", 31.034661, 121.612282)
.order(SortOrder.ASC) // 指定排序顺序为升序
.unit(DistanceUnit.KILOMETERS) // 距离单位为千米
);
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(search);
}
/**
* 条件排序 按照积分高 费用低排序
* @throws Exception
*/
@Test
public void sorcePriceTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为地理距离查询,在字段"location"附近半径5公里内的文档
searchRequest.source()
.query(QueryBuilders
.geoDistanceQuery("location") // 指定地理字段
.point(31.240001, 121.509106) // 圆心坐标
.distance("5", DistanceUnit.KILOMETERS))// 距离和单位
.sort("score", SortOrder.DESC) // 按照积分从高到低排序
.sort("price",SortOrder.ASC); // 按照价格从低到高排序
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(search);
}
/**
* bool
* 复合多条件查询
* !!!!必须掌握!!!!
* @throws Exception
*/
@Test
public void boolTest()throws Exception{
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 构建布尔查询,包括must(必须匹配)、mustNot(不能匹配)和filter(过滤器)子句
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// 添加must子句:匹配字段"all"包含"如家"的文档
boolQueryBuilder.must(QueryBuilders.matchQuery("all", "如家"));
// 添加mustNot子句:排除价格大于400的文档
boolQueryBuilder.mustNot(QueryBuilders.rangeQuery("price").gt(400));
// 添加filter子句:筛选距离坐标点(31.21,121.5)不超过10公里的文档
boolQueryBuilder.filter(QueryBuilders.geoDistanceQuery("location")
.point(31.21, 121.5)
.distance(10, DistanceUnit.KILOMETERS));
// 设置搜索请求的查询条件为布尔查询
searchRequest.source()
.query(boolQueryBuilder);
// 执行搜索请求并获取搜索响应
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(response);
}
/**
* 算分查询
* @throws Exception
*/
@Test
public void functionScoreTest() throws Exception {
// 创建一个用于搜索的请求对象,指定索引为 EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 创建 FunctionScoreQueryBuilder,它用于结合不同的查询条件并应用自定义的评分函数
FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(
QueryBuilders.termQuery("city", "深圳"), // 查询条件:城市为深圳
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
QueryBuilders.termQuery("brand", "如家"), // 酒店品牌为如家
ScoreFunctionBuilders.weightFactorFunction(10) // 自定义评分函数,权重为10
)
}
).boostMode(CombineFunction.MULTIPLY); // 设置评分的组合方式为 MULTIPLY(相乘)
// 配置搜索请求的内容,将 FunctionScoreQueryBuilder 作为查询条件
searchRequest.source()
.query(functionScoreQueryBuilder);
// 执行搜索请求
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 打印搜索结果
printResult(response);
}
/**
* 根据经纬度查询
*
* @throws Exception
*/
@Test
public void geoDistanceTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为地理距离查询,在字段"location"附近半径5公里内的文档
searchRequest.source()
.query(QueryBuilders
.geoDistanceQuery("location") // 指定地理字段
.point(31.240001, 121.509106) // 圆心坐标
.distance("5", DistanceUnit.KILOMETERS)); // 距离和单位
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(search);
}
/**
* range 查询
*
* @throws Exception
*/
@Test
public void rangeTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为范围查询,匹配字段"price"在大于等于400且小于500的文档
searchRequest.source()
.query(QueryBuilders.rangeQuery("price").gte(400).lt(500));
// 执行搜索请求并获取搜索响应
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(response);
}
/**
* term 精准查询
*
* @throws Exception
*/
@Test
public void termTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为精确匹配,匹配字段"brand"的值为"如家"
searchRequest.source()
.query(QueryBuilders.termQuery("brand", "如家"));
// 执行搜索请求并获取搜索响应
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取搜索结果中的总命中数
long value = response.getHits().getTotalHits().value;
System.out.println("total:" + value);
// 获取搜索结果中的命中文档数组
SearchHit[] hits = response.getHits().getHits();
// 遍历命中文档数组并输出每个文档的 JSON 内容
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
System.out.println(json);
}
}
/**
* nulti_match
*
* @throws Exception
*/
@Test
public void multiMatchTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为多字段匹配,搜索关键词为"北京",匹配字段包括"name"、"city"和"business"
searchRequest.source()
.query(QueryBuilders.multiMatchQuery("北京", "name", "city", "business"));
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(search);
}
/**
* 全文检索,分词后搜索
*
* @throws Exception
*/
@Test
public void matchTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为匹配指定字段"all"中包含"7天酒店"的文档
searchRequest.source()
.query(QueryBuilders.matchQuery("all", "7天酒店"));
// 执行搜索请求并获取搜索响应
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
// 调用printResult方法打印搜索结果
printResult(response);
}
/**
* 查询所有
*
* @throws Exception
*/
@Test
public void matchAllTest() throws Exception {
// 创建一个搜索请求,指定索引为EsConstant.HOTEL_INDEX
SearchRequest searchRequest = new SearchRequest(EsConstant.HOTEL_INDEX);
// 设置搜索请求的查询条件为匹配所有文档
searchRequest.source().query(QueryBuilders.matchAllQuery());
// 执行搜索请求并获取搜索响应
SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
// 获取搜索结果中的总命中数
long value = search.getHits().getTotalHits().value;
System.out.println("总数:" + value);
// 获取搜索结果中的命中文档数组
SearchHit[] hits = search.getHits().getHits();
// 遍历命中文档数组并输出每个文档的 JSON 内容
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
System.out.println(json);
}
}
/**
* 打印结果的方法
*/
private void printResult(SearchResponse response) {
// 获取搜索结果中的总命中数
long total = response.getHits().getTotalHits().value;
System.out.println("总数为:" + total);
// 获取搜索结果中的命中文档数组
SearchHit[] hits = response.getHits().getHits();
// 遍历命中文档数组并输出每个文档的 JSON 内容
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
System.out.println(json);
}
}
}