目录
5.批量操作(用于把所有数据的数据同步到ES索引库,先查询出所有然后一条条添加)
5.算分函数 (增加分值,让分值大于别人,排名时会在最前面)
零、实现搜索业务,肯定离不开RestHighLevelClient,我们需要把它注册到Spring中作为一个Bean。
@Bean
public RestHighLevelClient client(){
return new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.136.134:9200")
));
}
一、操作索引库(用不到)
1.判断索引库是否存在
@Test
void testExistsHotelIndex() throws IOException {
// 1.创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
// 2.发送请求
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
// 3.输出
System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
}
2.删除索引库
@Test
void testDeleteHotelIndex() throws IOException {
// 1.创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
// 2.发送请求
client.indices().delete(request, RequestOptions.DEFAULT);
}
3.创建索引库
@Test
void createHotelIndex() throws IOException {
// 1.创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
// 2.准备请求的参数:DSL语句
request.source(HotelConstants.MAPPING_TEMPLATE, XContentType.JSON);
// 3.发送请求
client.indices().create(request, RequestOptions.DEFAULT);
}
4.小结
索引库操作的基本步骤:
- 初始化`RestHighLevelClient`
- 创建`XxxIndexRequest`。XXX是Get、Create、Delete
- 准备参数(Create需要)
- 发送请求。调用`RestHighLevelClient.indices().xxx()`方法,xxx是exists、create、delete
二、操作文档(重点)
1.新增文档(数据库新增数据的时候 同步es索引库)
//添加文档
@Test
public void testAddDocument() throws Exception {
//1 准备文档数据
// 1-1 根据id查询酒店数据
Hotel hotel = hotelMapper.selectById(61083L);
// 1-2 转为文档实体类型
HotelDoc hotelDoc = new HotelDoc(hotel);
// 1-3 将hotelDoc转为json
String json = JSON.toJSONString(hotelDoc);
//2 创建request
IndexRequest request = new IndexRequest("hotel").id(hotelDoc.getId().toString());
request.source(json, XContentType.JSON);
//3 发送请求
client.index(request, RequestOptions.DEFAULT);
}
2.查询文档
//根据id查询文档
@Test
public void testGetDocumentById()throws Exception{
// 1.创建request
GetRequest request = new GetRequest("hotel", "61083");
// 2.发送请求,得到响应
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 3.解析响应
String json = response.getSourceAsString();
// 4.json转为hotelDoc
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
System.out.println(hotelDoc);
}
3.修改文档
//根据id修改指定文档
@Test
public void testUpdateDocument() throws Exception {
// 1.创建request
UpdateRequest request = new UpdateRequest("hotel", "61083");
//2. 设置更新内容
Map<String, Object> map = new HashMap<>();
map.put("price", 339);
map.put("starName", "四钻");
request.doc(map);
// 3.发送请求
client.update(request, RequestOptions.DEFAULT);
}
4.删除文档
//删除文档
@Test
void testDeleteDocument() throws IOException {
// 1.创建Request
DeleteRequest request = new DeleteRequest("hotel", "61083");
// 2.发送请求
client.delete(request, RequestOptions.DEFAULT);
}
5.批量操作(用于把所有数据的数据同步到ES索引库,先查询出所有然后一条条添加)
//批量操作
@Test
public void testBulkRequest() throws Exception {
// 查询所有酒店数据
List<Hotel> hotelList = hotelMapper.selectList(null);
// 1.创建request
BulkRequest request = new BulkRequest();
// 2.准备DSL
for (Hotel hotel : hotelList) {
// 2-1 转为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel);
// 2-2 转为json
String json = JSON.toJSONString(hotelDoc);
// 2-3 新增document
request.add(
new IndexRequest("hotel")
.id(hotelDoc.getId().toString())
.source(json, XContentType.JSON)
);
}
// 3.发送请求
client.bulk(request, RequestOptions.DEFAULT);
}
6.小结
文档操作的基本步骤:
- 初始化`RestHighLevelClient`
- 创建`XxxRequest`。XXX是Index、Get、Update、Delete、Bulk
- 准备参数(Index、Update、Bulk时需要)
- 发送请求。调用`RestHighLevelClient..xxx()`方法,xxx是index、get、update、delete、bulk
- 解析结果(Get时需要)
三、查询文档
1.查询所有
//查询
@Test
public void testMatchAll() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
request.source().query(
QueryBuilders.matchAllQuery() //match_all
);
//3. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 解析结果
System.out.println("结果数量:" + response.getHits().getTotalHits().value);//hits.total.value
SearchHit[] searchHitArr = response.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String source = searchHit.getSourceAsString();//hits.hits.get(n).source
System.out.println(JSON.parseObject(source, HotelDoc.class));
}
}
elasticsearch返回的结果是一个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文档数据
2.全文检索
//查询 match
@Test
public void testMatch() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
request.source().query(
QueryBuilders.matchQuery("all", "外滩如家") //match
);
//3. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 解析结果
System.out.println("结果数量:" + response.getHits().getTotalHits().value);
SearchHit[] searchHitArr = response.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String source = searchHit.getSourceAsString();
System.out.println(JSON.parseObject(source, HotelDoc.class));
}
}
//查询 multi_match
@Test
public void testMultiMatch() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
request.source().query(
QueryBuilders.multiMatchQuery("外滩如家", "brand", "business", "name")
);
//3. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 解析结果
System.out.println("结果数量:" + response.getHits().getTotalHits().value);
SearchHit[] searchHitArr = response.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String source = searchHit.getSourceAsString();
System.out.println(JSON.parseObject(source, HotelDoc.class));
}
}
3.精确查询
①词条查询
//词条查询 term
@Test
public void testTerm() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
request.source().query(
QueryBuilders.termQuery("city", "北京")
);
//3. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 解析结果
System.out.println("结果数量:" + response.getHits().getTotalHits().value);
SearchHit[] searchHitArr = response.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String source = searchHit.getSourceAsString();
System.out.println(JSON.parseObject(source, HotelDoc.class));
}
}
②范围查询
//范围查询 range
@Test
public void testRange() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 构建条件,并添加到查询请求
request.source().query(
rangeQuery("price").gte(1000).lte(2000)
);
//3. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 解析结果
System.out.println("结果数量:" + response.getHits().getTotalHits().value);
SearchHit[] searchHitArr = response.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String source = searchHit.getSourceAsString();
System.out.println(JSON.parseObject(source, HotelDoc.class));
}
}
4.地理查询
①矩形范围
//矩形范围查询 geo_bounding_box
@Test
public void testGeoBoundingBox() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
request.source().query(
QueryBuilders.geoBoundingBoxQuery("location")
.setCorners(new GeoPoint(40.08, 116.47), new GeoPoint(39.9, 116.405))
);
//3. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 解析结果
System.out.println("结果数量:" + response.getHits().getTotalHits().value);
SearchHit[] searchHitArr = response.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String source = searchHit.getSourceAsString();
System.out.println(JSON.parseObject(source, HotelDoc.class));
}
}
②圆形范围
//矩形圆形查询 geo_distance
@Test
public void testGeoDistance() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
request.source().query(
QueryBuilders.geoDistanceQuery("location")
.point(39.9, 116.405)//设置圆心坐标
.distance("15km")//设置半径
);
//3. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 解析结果
System.out.println("结果数量:" + response.getHits().getTotalHits().value);
SearchHit[] searchHitArr = response.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String source = searchHit.getSourceAsString();
System.out.println(JSON.parseObject(source, HotelDoc.class));
}
}
5.算分函数 (增加分值,让分值大于别人,排名时会在最前面)
//算分函数
@Test
public void testFunctionScore() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
request.source().query(
QueryBuilders.functionScoreQuery(
QueryBuilders.matchQuery("all", "北京"),//设置查询条件
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ //设置算分函数
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
QueryBuilders.termQuery("brand", "凯悦"),
ScoreFunctionBuilders.weightFactorFunction(10)
)
}
).boostMode(CombineFunction.MULTIPLY) //设置算法策略
);
//3. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 解析结果
System.out.println("结果数量:" + response.getHits().getTotalHits().value);
SearchHit[] searchHitArr = response.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String source = searchHit.getSourceAsString();
System.out.println(JSON.parseObject(source, HotelDoc.class));
}
}
6.复合查询(用于有多个查询条件的时候)
//布尔查询
@Test
public void testBoolean() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
request.source().query(
QueryBuilders.boolQuery().must(
QueryBuilders.termQuery("name", "如家")
).mustNot(
QueryBuilders.rangeQuery("price").gte(400)
).filter(
QueryBuilders.geoDistanceQuery("location")
.point(39.9, 116.405)//设置圆心坐标
.distance("10km")//设置半径
)
);
//3. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 解析结果
System.out.println("结果数量:" + response.getHits().getTotalHits().value);
SearchHit[] searchHitArr = response.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String source = searchHit.getSourceAsString();
System.out.println(JSON.parseObject(source, HotelDoc.class));
}
}
7.排序、分页
// 排序 分页
@Test
public void testPageAndSort() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
request.source().query(QueryBuilders.matchAllQuery());
request.source().sort("price", SortOrder.ASC);//排序,按照价格正序排
request.source().from(0).size(10);//分页,从0查,查10个;
//3. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4. 解析结果
System.out.println("结果数量:" + response.getHits().getTotalHits().value);
SearchHit[] searchHitArr = response.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String source = searchHit.getSourceAsString();
System.out.println(JSON.parseObject(source, HotelDoc.class));
}
}
8.高亮(几乎所有搜索项目都要加)
//高亮
@Test
public void testHighLight() throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
request.source().query(QueryBuilders.matchQuery("all", "如家"));
//3. 设置高亮效果
request.source().highlighter(
new HighlightBuilder()
.field("name")
.preTags("<em>")
.postTags("</em>").
requireFieldMatch(false)
);
//4. 执行请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//5. 解析结果
SearchHit[] searchHits = response.getHits().getHits();
for (SearchHit searchHit : searchHits) {
//没有效果的对象
HotelDoc hotelDoc = JSON.parseObject(searchHit.getSourceAsString(), HotelDoc.class);
//解析效果,替换上面对象中的title的值
String name = (searchHit.getHighlightFields().get("name").getFragments())[0].toString();
hotelDoc.setName(name);
System.out.println(hotelDoc);
}
}
四、项目实例,建议直接跳转
@Override
public PageResult list(RequestParams requestParams) throws IOException {
//1. 构建查询请求
SearchRequest request = new SearchRequest("hotel");
//2. 设置查询条件
//2-1创建复合查询
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//2-2 获取搜索关键字, 设置为查询条件
String key = requestParams.getKey();
if (StringUtils.isEmpty(key)) {
boolQuery.must(QueryBuilders.matchAllQuery());
} else {
boolQuery.must(QueryBuilders.matchQuery("all", key));
}
//2-3 获取城市、星级、品牌、价格,使用过滤语法筛选
// 城市
if (StrUtil.isNotEmpty(requestParams.getCity())) {
boolQuery.filter(QueryBuilders.termQuery("city", requestParams.getCity()));
}
// 星级
if (StrUtil.isNotEmpty(requestParams.getStarName())) {
boolQuery.filter(QueryBuilders.termQuery("starName", requestParams.getStarName()));
}
// 品牌
if (StrUtil.isNotEmpty(requestParams.getBrand())) {
boolQuery.filter(QueryBuilders.termQuery("brand", requestParams.getBrand()));
}
// 价格
if (requestParams.getMinPrice() != null && requestParams.getMaxPrice() != null) {
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(requestParams.getMinPrice()).lte(requestParams.getMaxPrice()));
}
//2-4 使用算分函数 提供广告酒店的排名
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(boolQuery,
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
QueryBuilders.termQuery("isAD", true),
ScoreFunctionBuilders.weightFactorFunction(5))
}
);
request.source().query(functionScoreQuery);
//3. 设置分页
Integer pageNum = requestParams.getPage();
Integer pageSize = requestParams.getSize();
request.source().from((pageNum - 1) * pageSize).size(pageSize);
//4. 设置排序
String location = requestParams.getLocation();
if (StrUtil.isNotEmpty(location)) {
request.source().sort(
SortBuilders.geoDistanceSort("location", new GeoPoint(location))//设置核心坐标位置
.order(SortOrder.ASC) //设置排序方式
.unit(DistanceUnit.KILOMETERS)// 单位
);
}
//5. 设置高亮
request.source().highlighter(
new HighlightBuilder().field("name").requireFieldMatch(false)
);
//6. 发起请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//7. 处理结果
return handleResponse(response);
}
五、没用
# 测试默认分词器
GET /_analyze
{
"analyzer": "standard",
"text": "程序员学习java太棒了"
}
# 测试IK分词器
GET /_analyze
{
"analyzer": "ik_smart",
"text": "程序员学习java太棒了"
}
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "程序员学习java太棒了"
}
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "程序员的Java就业超过90%,奥力给了!"
}
# 创建索引库
PUT /course
# 查看索引库
GET /course
# 删除索引库
DELETE /course
# 创建索引库
PUT /course
# 自定义映射
PUT /course/_mapping/
{
"properties":{
"title":{
"type":"text",
"analyzer":"ik_max_word"
},
"images":{
"type":"keyword",
"index":false
},
"price":{
"type":"float"
},
"name":{
"properties":{
"firstName":{
"type":"keyword"
},
"lastName":{
"type":"keyword"
}
}
}
}
}
# 查看映射
GET /course/_mapping
# 创建索引库的时候同时设置映射
PUT /course/
{
"mappings": {
"properties":{
"title":{
"type":"text",
"analyzer":"ik_max_word"
},
"images":{
"type":"keyword",
"index":false
},
"price":{
"type":"float"
},
"name":{
"properties":{
"firstName":{
"type":"keyword"
},
"lastName":{
"type":"keyword"
}
}
}
}
}
}
# 创建文档
POST /course/_doc/1
{
"title":"小米手机",
"price":2000,
"images":"http://image.xiaomi.com/1.jpg",
"name":{
"firstName":"三",
"lastName":"张"
}
}
# 创建文档
POST /course/_doc
{
"title":"华为手机",
"price":3000,
"images":"http://image.huawei.com/1.jpg",
"name":{
"firstName":"四",
"lastName":"李"
}
}
# 根据主键查询
GET /course/_doc/1
GET /course/_doc/Wn37GoMBJhQmYmwV_1pK
# 全量修改,语法跟新增一致
POST /course/_doc/1
{
"title":"小米手机",
"price":2500,
"images":"http://image.xiaomi.com/1.jpg",
"name":{
"firstName":"三",
"lastName":"张"
}
}
# 增量修改
POST /course/_update/1
{
"doc": {
"title":"大米手机",
"price":2500
}
}
# 删除
DELETE /course/_doc/1
# 查询所有
GET /hotel/_search
{
"query": {
"match_all": {}
}
}
# 全文检索(分词) 针对于一个字段
GET /hotel/_search
{
"query": {
"match": {
"all": "外滩如家"
}
}
}
# 全文检索(分词) 针对于多个字段
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "外滩如家",
"fields": ["name","brand","business"]
}
}
}
# 精确查询(term)
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "北京"
}
}
}
}
# 精确查询(range)
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 1000,
"lte": 2000
}
}
}
}
# 矩形范围
GET /hotel/_search
{
"query": {
"geo_bounding_box":{
"location": {
"top_left": {
"lat": 40.08,
"lon": 116.47
},
"bottom_right": {
"lat": 39.9,
"lon": 116.405
}
}
}
}
}
# 圆形范围
GET /hotel/_search
{
"query": {
"geo_distance":{
"distance":"15km",
"location":"39.9,116.405"
}
}
}
# 默认查询
GET /hotel/_search
{
"query": {
"match": {
"all": "北京"
}
}
}
# 算分函数查询
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"match": {
"all": "北京"
}
},
"functions": [
{
"filter": {
"term": {
"brand": "凯悦"
}
},
"weight": 20
}
],
"boost_mode": "multiply"
}
}
}
# 复合查询
# 需求:搜索名字包含“如家”,价格不高于400,在坐标39.9,116.405周围10km范围内的酒店
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"name": {
"value": "如家"
}
}
}
],
"must_not": [
{
"range": {
"price": {
"gt": 400
}
}
}
],
"filter": [
{
"geo_distance": {
"distance": "10km",
"location": {
"lat": 39.9,
"lon": 116.405
}
}
}
]
}
}
}
# 按照价格正序排列
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "asc"
}
},
{
"score": "desc"
}
]
}
# 按照距离远近排列(需要指定的是参考点)
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 39.91512,
"lon": 116.40396
},
"order": "asc"
}
}
]
}
# 分页
GET /hotel/_search
{
"query": {
"match_all": {}
},
"from": 9990,
"size": 11
}
# 高亮
GET /hotel/_search
{
"query": {
"match": {
"name": "如家"
}
},
"highlight": {
"fields": {
"name":{
"pre_tags":"<em>",
"post_tags":"</em>"
}
}
}
}
GET /hotel/_search
{
"query": {
"match": {
"all": "如家"
}
},
"highlight": {
"fields": {
"name":{
"pre_tags":"<i>",
"post_tags":"</i>",
"require_field_match": "false"
}
}
}
}
#聚合为桶
GET /hotel/_search
{
"query":{ //搜索条件
"range": {
"price":{
"lte": 500
}
}
},
"size": 0, //不查询具体的数据
"aggs": { //声明这是一个聚合查询,是aggregations的缩写
"brandAgg": { //给这次聚合起一个名字,可任意指定
"terms": { //聚合的类型,这里选择terms,是根据词条内容(这里是品牌)划分
"field": "brand", //按照哪个字段分组
"size": 10, //显示多少条聚合结果
"order": {
"_count": "asc" //分组之后可以根据数量排序
}
}
}
}
}
#桶内度量
GET /hotel/_search
{
"query":{
"match_all": {}
},
"size": 0, //不查询具体的数据
"aggs": {
"brandAgg": {
"terms": {
"field": "brand", //按照哪个字段分组
"order": {
"scoreAgg.avg": "desc" //根据指定的统计项排序
}
},
"aggs":{ //是brands聚合的子聚合,也就是分组后对每组分别计算
"scoreAgg": {//聚合名称
"stats": {//聚合类型,这里stats可以计算min、max、avg等
"field":"score"//聚合字段,这里是score
}
}
}
}
}
}
# 设置分词器
PUT /test
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "ik_max_word",
"filter": "py"
}
},
"filter": {
"py": {
"type": "pinyin",
"keep_full_pinyin": false,
"keep_joined_full_pinyin": true,
"keep_original": true,
"limit_first_letter_length": 16,
"remove_duplicated_term": true,
"none_chinese_pinyin_tokenize": false
}
}
}
},
"mappings": {
"properties": {
"name":{
"type": "text",
"analyzer": "my_analyzer",
"search_analyzer": "ik_smart"
}
}
}
}
# 创建模拟数据
POST /test/_doc/1
{
"id": 1,
"name": "狮子"
}
POST /test/_doc/2
{
"id": 2,
"name": "虱子"
}
# 重新查询
GET /test/_search
{
"query": {
"match": {
"name": "shizi"
}
}
}
GET /test/_search
{
"query": {
"match": {
"name": "狮子"
}
}
}