目录
1.ElasticSearch简介
ElasticSearch是一个开源的、支持分布式的、能够进行近实时搜索和分析、能够处理PB级数据量的搜索引擎。目前有很多平台和工作场景会涉及到对ElasticSearch的使用。
2.ElasticSearch与MySQL区别
-
Mysql数据操作具备事务性,而ElasticSearch没有
-
MySQL支持外键,而ElasticSearch不支持
-
Mysql采用B+树索引,而ElasticSearch采用倒排索引
3.ElasticSearch核心概念介绍
ES在使用时,会涉及到五大核心概念:索引(Index)、映射(Mapping)、域(Field)、文档(Documen)、倒排索引。以一张MySQL中数据表为例:
ElasticSearch | Mysql |
---|---|
索引(Index) | 表(Table) |
映射(Mapping) | 表结构 |
域(Field) | 字段列(Column) |
文档(Document) | 一条数据(Row) |
3.1索引(Index)
索引相当于关系型数据库中的一张表,一个index中包含若干个document,通过index代替一类类似的或相同的document。
3.1.1添加索引
# PUT 索引名称
PUT person
3.1.2查询索引
#查询单个索引 GET 索引名称
GET person
# 查询多个索引信息 GET 索引名称,索引名称
PUT person1
GET person,person1
# 查询所有索引信息
GET _all
3.1.3删除索引
#删除索引 DELETE 索引名称
DELETE person1
3.2域(Field)
域(Field)相当于数据表中的字段列,当有了索引之后,需要在索引中设置域的相关信息如:名称,类型等。这个过程称为:映射(Mapping)。相当于关系型数据库中设置表的信息。
3.2.1数据类型
字符串
-
text:会进行分词,如小米手机,会分成:小米,手机。 被分出来的每一个词,称为term(词条)
-
keyword:不会进行分词,如小米手机,只有一个词条,即小米手机。
数值
-
long:带符号64位整数
-
integer:带符号32位整数
-
short:带符号16位整数
-
byte:带符号8位整数
-
double:双精度64位浮点数
-
float:单精度32位浮点数
-
half_float:半精度16位浮点数
布尔:
-
boolean
二进制:
-
binary
日期:
-
date
范围类型:
-
integer_range
-
float_range
-
long_range
-
double_range
-
date_range
数组
对象
3.2.2添加映射
(1)为已存在的索引库添加映射
PUT person/_mapping
{
"properties":{
"name":{
"type":"text"
},
"age":{
"type":"integer"
}
}
}
#查看索引映射信息
GET person/_mapping
(2)创建索引并添加映射
#创建索引并添加映射
PUT person
{
"mappings": {
"properties": {
"name":{
"type": "text"
},
"age":{
"type": "integer"
}
}
}
}
(3)添加字段
对于映射,只能进行字段添加,不能对字段进行修改或删除,如有需要,则重新创建映射。
#添加字段
PUT person/_mapping
{
"properties":{
"name": {
"type": "text"
},
"age": {
"type": "integer"
},
"address":{
"type":"text"
}
}
}
3.3文档(Document)
ES中最小的数据单元,代表索引中的一条数据,通常是使用json的数据格式表示的
3.3.1添加文档
(1)添加文档,手动设置id
#添加文档,手动设置id
POST person/_doc/1
{
"name":"朱吴",
"age":"23",
"address":"南昌"
}
(2)添加文档,自动生成id
#添加文档,自动生成id
POST person/_doc
{
"name":"朱徐",
"age":"25",
"address":"深圳"
}
3.3.2查询文档
(1)根据id查询文档
#GET 索引名称/_doc/文档id
GET person/_doc/1
(2)查询所有文档
#GET 索引名称/_search
GET person/_search
3.3.3修改文档
PUT person/_doc/1
{
"name":"朱武",
"age":"26",
"address":"深圳"
}
3.3.4删除文档
#DELETE 索引名称/_doc/文档id
DELETE person/_doc/1
3.4倒排索引
要想理解倒排索引,首先思考一个问题,获取某个文件夹下所有文件名中包含Spring的文件
1)确定要搜索的文件夹 2)遍历文件夹下所有文件 3)判断文件名中是否包含Spring
这种思维可以理解为是一种正向思维的方式,从外往内,根据key找value。这种方式可以理解为正向索引。
而ElasticSearch为了提升查询效率,采用反向思维方式,根据value找key。
4.Springboot整合ElasticSearch
4.1批量导入操作
索引及映射准备
PUT hotel
{
"mappings": {
"properties": {
"name":{
"type": "text",
"analyzer": "ik_max_word"
},
"address":{
"type": "text",
"analyzer": "ik_max_word"
},
"brand":{
"type": "keyword"
},
"type":{
"type": "keyword"
},
"price":{
"type": "integer"
},
"specs":{
"type": "keyword"
},
"salesVolume":{
"type": "integer"
},
"area":{
"type": "text",
"analyzer": "ik_max_word"
},
"imageUrl":{
"type": "text"
},
"synopsis":{
"type": "text",
"analyzer": "ik_max_word"
},
"createTime":{
"type": "date",
"format": "yyyy-MM-dd"
},
"isAd":{
"type":"integer"
}
}
}
}
在ES中提供了批量操作的方式:例如
#新增一号用户
#删除一号用户
#新增二号用户
POST _bulk
{"create":{"_index":"user","_id":"1"}}
{"name":"张三","age":18,"address":"北京"}
{"delete":{"_index":"user","_id":"1"}}
{"create":{"_index":"user","_id":"2"}}
{"name":"李四","age":20,"address":"黑龙江"}
在代码中,批量导入时,从MySQL中查出所有数据后批量导入ES
//批量导入
@Override
public int addDocToES() {
//todo 批量导入文档实现
// 1.创建批量导入对象
BulkRequest bulkRequest = new BulkRequest();
//2.从MySQL数据库中查出所有数据
List<HotelEntity> hotelList = hotelMapper.selectList(null);
//3.遍历,使用带有转换日期的JSON序列化并将数据加入到创建的导入对象中
for (HotelEntity hotelEntity : hotelList) {
//3.1将实体进行序列化
String data = JSON.toJSONStringWithDateFormat(hotelEntity, "yyyy-MM-dd", SerializerFeature.WriteDateUseDateFormat);
//3.2 实例化IndexRequest并设置索引名,文档内容(对象的JSON格式)
IndexRequest indexRequest = new IndexRequest("hotel").source(data, XContentType.JSON);
//3.3加入批量批量导入对象中
bulkRequest.add(indexRequest);
}
try {
//4.通过restHighLevelClient调用bulk方法,开启执行批量操作
BulkResponse responses = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
return responses.status().getStatus();
} catch (IOException e) {
e.printStackTrace();
}
return 0;
}
4.2查询操作
4.2.1查询所有
GET hotel/_search
{
"query": {
"match_all": {}
}
}
//查询全部
/*
GET hotel/_search
{
"query": {
"match_all": {}
}
}
*/
@Override
public Map<String, Object> matchAllQuery() {
//todo 查询全部文档实现
//1.创建搜索请求对象SearchRequest
SearchRequest searchRequest = new SearchRequest("hotel");
//2.创建searchSourceBuilder,进行筛选条件填充
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//2.1构建查询方式
QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
searchSourceBuilder.query(queryBuilder);
searchRequest.source(searchSourceBuilder);
//3.通过restHighLevelClient调用search方法,开启查询
try {
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//4.处理查询结果
//4.1获取查询结果
SearchHits hits = searchResponse.getHits();
long totalHits = hits.getTotalHits().value;
SearchHit[] hits1 = hits.getHits();
//4.2,反序列化后添加到集合中
List<HotelEntity>list = new ArrayList<>();
for (SearchHit searchHit : hits1) {
String sourceAsString = searchHit.getSourceAsString();
list.add(JSON.parseObject(sourceAsString,HotelEntity.class));
}
//4.3封装成map
Map<String,Object> map = new HashMap<>();
map.put("list",list);
map.put("totalResultSize",totalHits);
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.2分页查询所有
GET hotel/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5
}
public Map<String, Object> pageQuery(int current, int size) {
//todo 分页查询文档实现
//1.创建搜索请求对象SearchRequest
SearchRequest searchRequest = new SearchRequest("hotel");
//2.创建SearchSourceBuilder,进行筛选条件填充
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//2.1构建查询方式
MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
searchSourceBuilder.query(queryBuilder);
//2.2设置分页
searchSourceBuilder.from((current-1)*size);
searchSourceBuilder.size(size);
searchRequest.source(searchSourceBuilder);
//3.执行查询
try {
SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
//4.处理查询结果
SearchHits searchHits = searchResponse.getHits();
long hitsTotal = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelEntity> list = new ArrayList<>();
for (SearchHit searchHit : hits){
String sourceAsString = searchHit.getSourceAsString();
list.add(JSON.parseObject(sourceAsString,HotelEntity.class));
}
Map<String, Object> map = new HashMap<>();
map.put("list", list);
map.put("totalResultSize", hitsTotal);
map.put("current", current);
//设置总页数
map.put("totalPage",(hitsTotal+size-1)/size);
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.3精确搜索
termQuery:不会对查询条件进行分词(keyword)
GET hotel/_search
{
"query": {
"term": {
"brand": {
"value": "万豪"
}
}
}
}
GET hotel/_search
{
"query": {
"term": {
"brand": "万豪"
}
}
}
public Map<String, Object> brandTermQuery(int current, int size, Map<String, Object> searchParam) {
//todo 按品牌精确查询实现
//1.构建查询请求对象SearchRequest
SearchRequest searchRequest = new SearchRequest("hotel");
//2.创建SearchSourceBuilder,进行筛选条件填充
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//2.1构建查询方式
if(!StringUtils.isEmpty(searchParam.get("brand"))){
QueryBuilder queryBuilder = QueryBuilders.termQuery("brand", searchParam.get("brand").toString());
searchSourceBuilder.query(queryBuilder);
}
//2.2设置分页
searchSourceBuilder.from((current - 1)*size);
searchSourceBuilder.size(size);
searchRequest.source(searchSourceBuilder);
//3.执行查询
try {
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//4.处理查询结果
SearchHits searchHits = searchResponse.getHits();
long hitsTotal = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelEntity> list = new ArrayList<>();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
}
//5.封装返回结果
Map<String,Object> map = new HashMap<>();
map.put("list",list);
map.put("totalResultSize",hitsTotal);
map.put("current",current);
//6.设置总页数
map.put("totalPage",(hitsTotal+size-1)/size);
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.3分词匹配查询
GET hotel/_search
{
"query": {
"match": {
"name":"北京市东城区万豪酒店"
}
}
}
public Map<String, Object> nameMatchQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//todo 根据酒店名称匹配查询实现
//1.构建查询请求对象
SearchRequest searchRequest = new SearchRequest("hotel");
//2.创建SearchSourceBuilder,进行筛选条件填充
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//3.查询条件构造
MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", searchParam.get("name"));
searchSourceBuilder.query(queryBuilder);
//4.设置分页
searchSourceBuilder.from((current-1)*size);
searchSourceBuilder.size(size);
searchRequest.source(searchSourceBuilder);
try {
//5.执行查询
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//6.处理查询结果
SearchHits searchHits = searchResponse.getHits();
//6.1命中结果总数
long hitsTotal = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelEntity> list = new ArrayList<>();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
list.add(JSON.parseObject(sourceAsString,HotelEntity.class));
}
//6.2.封装返回结果
Map<String,Object> map = new HashMap<>();
map.put("list",list);
map.put("totalResultSize",hitsTotal);
map.put("current",current);
//6.3.设置总页数
map.put("totalPage",(hitsTotal+size-1)/size);
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.4模糊查询
wildcardQuery:会对查询条件进行分词。还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)
GET hotel/_search
{
"query": {
"wildcard": {
"brand": {
"value": "美*"
}
}
}
}
public Map<String, Object> nameWildcardQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//todo 根据酒店名称模糊查询
//1.构建查询对象searchRequest
SearchRequest searchRequest = new SearchRequest("hotel");
//2.构造searchSourceBuilder,构造DSL
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
WildcardQueryBuilder queryBuilder = QueryBuilders.wildcardQuery("brand", searchParam.get("name") + "*");
sourceBuilder.query(queryBuilder);
//3.设置分页
sourceBuilder.from((current-1)*size);
sourceBuilder.size(size);
searchRequest.source(sourceBuilder);
try {
//4.执行查询
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//5.处理查询结果
SearchHits searchHits = searchResponse.getHits();
long hitsTotal = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelEntity> list = new ArrayList<>();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
list.add(JSON.parseObject(sourceAsString,HotelEntity.class));
}
//6.封装返回信息
Map<String,Object> map = new HashMap<>();
map.put("list",list);
map.put("totalResultSize", hitsTotal);
map.put("current", current);
//设置总页数
map.put("totalPage", (hitsTotal + size - 1) / size);
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.5多域搜索
queryStringQuery:
•会对查询条件进行分词。
•然后将分词后的查询条件和词条进行等值匹配
•默认取并集(OR)
•可以指定多个查询字段
GET hotel/_search
{
"query": {
"query_string": {
"fields": ["name","synopsis","area","address"],
"query": "如心 OR spa"
}
}
}
public Map<String, Object> searchQueryStringQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//构建查询对象
SearchRequest searchRequest = new SearchRequest("hotel");
//构建searchSourceBuilder,设置DSL
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//根据name,synopsis,area,address进行多域查询
QueryBuilder queryBuilder = QueryBuilders.queryStringQuery(searchParam.get("condition").toString())
.field("name")
.field("synopsis")
.field("area")
.field("address")
.defaultOperator(Operator.OR);
searchSourceBuilder.query(queryBuilder);
//设置分页
searchSourceBuilder.from((current-1)*size);
searchSourceBuilder.size(size);
searchRequest.source(searchSourceBuilder);
try {
//执行查询
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//处理查询结果
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelEntity> list = new ArrayList<>();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
list.add(JSON.parseObject(sourceAsString,HotelEntity.class));
}
//封装返回信息
Map<String,Object> map = new HashMap<>();
map.put("list", list);
map.put("totalResultSize", totalHits);
map.put("current", current);
//设置总页数
map.put("totalPage", (totalHits + size - 1) / size);
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.6搜索提示
(1)新建用于存储用户搜索记录的索引库
PUT suggest
{
"mappings": {
"properties": {
"name":{
"type": "completion",
"analyzer": "ik_max_word"
}
}
}
}
(2)数据查询
GET suggest/_search
{
"suggest": {
"my-suggest": {
"prefix": "酒店环境",
"completion": {
"field": "name",
"size":10
}
}
}
}
GET suggest/_search
{
"suggest": {
"my-suggest": {
"prefix": "酒店",
"completion": {
"field": "name",
"size":10
}
}
}
}
public List<String> searchSuggestInfo(String key) {
//定义结果集
List<String> result = new ArrayList<>();
//构建查询对象
SearchRequest searchRequest = new SearchRequest("suggest");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//构建自动补全搜索
SuggestBuilder suggestBuilder = new SuggestBuilder();
CompletionSuggestionBuilder suggestion= SuggestBuilders.completionSuggestion("name").text(key).size(10);
suggestBuilder.addSuggestion("my-suggest",suggestion);
searchSourceBuilder.suggest(suggestBuilder);
searchRequest.source(searchSourceBuilder);
try {
//执行查询
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//处理自动补全查询结果
Suggest suggest = searchResponse.getSuggest();
CompletionSuggestion completionSuggestion = suggest.getSuggestion("my-suggest");
List<CompletionSuggestion.Entry> entries = completionSuggestion.getEntries();
Map<String, Object> map = new HashMap<>();
if (entries!=null && entries.size()>0){
entries.forEach(entry -> {
if (!ObjectUtils.isEmpty(entry.getOptions())){
entry.forEach(action -> {
String suggestInfo = action.getText().toString();
if (suggestInfo.equals(key)){
return;
}
result.add(suggestInfo);
});
}else {
map.put("name",key);
}
});
}
//向索引库增量添加查询条件
if (!ObjectUtils.isEmpty(map)) {
IndexRequest indexRequest = new IndexRequest("suggest").source(map);
restHighLevelClient.index(indexRequest,RequestOptions.DEFAULT);
}
System.out.println("list" + result);
return result;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.7排序查询
#降序
GET hotel/_search
{
"sort": [
{
"salesVolume": {
"order": "desc"
}
}
]
}#升序
GET hotel/_search
{
"sort": [
{
"salesVolume": {
"order": "asc"
}
}
]
}
public Map<String, Object> salesSortQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//构建查询请求对象
SearchRequest searchRequest = new SearchRequest("hotel");
//构建SearchSourceBuilder,构建DSL
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
if ("desc".equalsIgnoreCase(searchParam.get("sortWay").toString())){
searchSourceBuilder.sort("salesVolume", SortOrder.DESC);
}else {
searchSourceBuilder.sort("salesVolume",SortOrder.ASC);
}
//设置分页
searchSourceBuilder.from((current-1)*size);
searchSourceBuilder.size(size);
searchRequest.source(searchSourceBuilder);
try {
//执行查询
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//处理查询结果
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelEntity> list = new ArrayList<>();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
list.add(JSON.parseObject(sourceAsString,HotelEntity.class));
}
//map封装
Map<String,Object> map = new HashMap<>();
map.put("list",list);
map.put("totalResultSize", totalHits);
map.put("current", current);
//设置总页数
map.put("totalPage", (totalHits + size - 1) / size);
map.put("sortWay",searchParam.get("sortWay"));
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.7范围查询
GET hotel/_search
{
"query": {
"range": {
"price": {
"gte": 500,
"lte": 2000
}
}
}
}
public Map<String, Object> priceRangeQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//创建查询请求对象
SearchRequest searchRequest = new SearchRequest("hotel");
//创建SearchSourceBuilder,设置DSL
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
RangeQueryBuilder queryBuilder = QueryBuilders.rangeQuery("price")
.gte(searchParam.get("minPrice"))
.lte(searchParam.get("maxPrice"));
searchSourceBuilder.query(queryBuilder);
//设置分页
searchSourceBuilder.from((current-1)*size);
searchSourceBuilder.size(size);
searchRequest.source(searchSourceBuilder);
try {
//执行查询
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//处理查询结果
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelEntity>list = new ArrayList<>();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
list.add(JSON.parseObject(sourceAsString,HotelEntity.class));
}
//map封装
Map<String,Object> map = new HashMap<>();
map.put("list",list);
map.put("totalResultSize", totalHits);
map.put("current", current);
//设置总页数
map.put("totalPage", (totalHits + size - 1) / size);
map.put("minPrice", searchParam.get("minPrice"));
map.put("maxPrice", searchParam.get("maxPrice"));
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.8多条件查询
boolQuery:对多个查询条件连接。
连接方式:
•must(and):条件必须成立
•must_not(not):条件必须不成立
•should(or):条件可以成立
•filter:条件必须成立,性能比must高。
#must单独使用 品牌必须是万豪,地区必须是北京
GET hotel/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"brand": {
"value": "万豪"
}
}
},
{
"term": {
"area": {
"value": "北京市"
}
}
}
]
}
}
}
# must与filter组合使用 查询品牌为万豪下的,区域为北京,价格范围在500和2000之间的酒店
GET hotel/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"brand": {
"value": "万豪"
}
}
}
],
"filter":[
{
"term": {
"area": "北京市"
}
},
{
"range":{
"price": {
"gte": 500,
"lte": 2000
}
}
}
]
}
}
}
#filter 单独使用 filter可以是单个条件,也可多个条件(数组形式)
GET hotel/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"brand": {
"value": "万豪"
}
}
}
]
}
}
}
public Map<String, Object> searchBoolQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//创建查询请求对象
SearchRequest searchRequest = new SearchRequest("hotel");
//创建 SearchSourceBuilder 对象,设置DSL
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//多条件查询 :多域、品牌精确、城市精确、星级精确、价格范围、销量排序
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//多域
if(!ObjectUtils.isEmpty(searchParam.get("condition"))){
QueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(searchParam.get("condition").toString())
.field("name")
.field("synopsis")
.field("area")
.field("address")
.defaultOperator(Operator.OR);
boolQueryBuilder.must(queryStringQueryBuilder);
}
//品牌精准
if(ObjectUtils.isNotEmpty(searchParam.get("brand"))){
QueryBuilder termQueryBuilder = QueryBuilders.termQuery("brand", searchParam.get("brand"));
boolQueryBuilder.filter(termQueryBuilder);
}
//城市精确
if (ObjectUtils.isNotEmpty(searchParam.get("area"))) {
QueryBuilder termQueryBuilder = QueryBuilders.termQuery("area", searchParam.get("area"));
boolQueryBuilder.filter(termQueryBuilder);
}
//星级精确
if (ObjectUtils.isNotEmpty(searchParam.get("specs"))) {
QueryBuilder termQueryBuilder = QueryBuilders.termQuery("specs", searchParam.get("specs"));
boolQueryBuilder.filter(termQueryBuilder);
}
//价格范围
if(ObjectUtils.isNotEmpty(searchParam.get("minPrice")) && !StringUtils.isEmpty(searchParam.get("maxPrice"))){
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price")
.gte(searchParam.get("minPrice"))
.lte(searchParam.get("maxPrice"));
boolQueryBuilder.filter(rangeQueryBuilder);
}
//销量排序
if (ObjectUtils.isNotEmpty(searchParam.get("sortWay"))){
if("desc".equalsIgnoreCase(searchParam.get("sortWay").toString())){
searchSourceBuilder.sort("salesVolume",SortOrder.DESC);
}else {
searchSourceBuilder.sort("salesVolume",SortOrder.ASC);
}
}
searchSourceBuilder.query(boolQueryBuilder);
//设置分页
searchSourceBuilder.from((current-1)*size);
searchSourceBuilder.size(size);
searchRequest.source(searchSourceBuilder);
try {
//执行查询
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//处理查询结果
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelEntity> list = new ArrayList<>();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
list.add(JSON.parseObject(sourceAsString,HotelEntity.class));
}
Map<String, Object> map = new HashMap<>();
map.put("list", list);
map.put("totalResultSize", totalHits);
map.put("current", current);
//设置总页数
map.put("totalPage", (totalHits + size - 1) / size);
map.put("brand", searchParam.get("brand"));
map.put("area", searchParam.get("area"));
map.put("specs", searchParam.get("specs"));
map.put("sortWay", searchParam.get("sortWay"));
map.put("minPrice", searchParam.get("minPrice"));
map.put("maxPrice", searchParam.get("maxPrice"));
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.9纠错查询
fuzzyQuery
自动纠错查询,会自动尝试将搜索条件进行纠错,然后去跟term进行匹配。
-
fuzziness:查询允许的最大编辑距离,默认为0
-
prefix_length:设置前几个字符不允许编辑
GET hotel/_search
{
"query": {
"fuzzy": {
"area": {
"value": "北经市",
"fuzziness": 1,
"prefix_length": 1
}
}
}
}
4.2.10高亮展示
如需将搜索条件以高亮形式展示,则需要在查询时,设置需要对哪一个域进行高亮展示
fields:指定要对哪个域高亮显示
pre_tags:设置高亮样式前缀
post_tags:设置高亮样式后缀
GET hotel/_search
{
"query": {
"term": {
"name": {
"value": "北京市"
}
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": "<font color = 'red'>",
"post_tags": "</font>"
}
}
}
}
public Map<String, Object> searchHighLight(Integer current, Integer size, Map<String, Object> searchParam) {
//创建查询请求对象
SearchRequest searchRequest = new SearchRequest("hotel");
//创建SearchSourceBuilder对象,设置查询条件DSL
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
if (ObjectUtils.isNotEmpty(searchParam.get("condition"))){
QueryBuilder queryBuilder = QueryBuilders.termQuery("name", searchParam.get("condition"));
searchSourceBuilder.query(queryBuilder);
}
//设置分页
searchSourceBuilder.from((current - 1) * size);
searchSourceBuilder.size(size);
//设置高亮显示
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("name");
highlightBuilder.preTags("<font color = 'red'>");
highlightBuilder.postTags("</font>");
searchSourceBuilder.highlighter(highlightBuilder);
searchRequest.source(searchSourceBuilder);
try {
//执行查询
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//处理查询结果
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelEntity> list = new ArrayList<>();
for (SearchHit hit : hits){
String sourceAsString = hit.getSourceAsString();
HotelEntity hotelEntity = JSON.parseObject(sourceAsString, HotelEntity.class);
//处理高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField highlightField = highlightFields.get("name");
if (highlightField!=null){
Text[] fragments = highlightField.getFragments();
hotelEntity.setName(fragments[0].toString());
}
list.add(hotelEntity);
}
Map<String, Object> map = new HashMap<>();
map.put("list", list);
map.put("totalResultSize", totalHits);
map.put("current", current);
//设置总页数
map.put("totalPage", (totalHits + size - 1) / size);
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
4.2.11加权查询
权重设置的两种方式:索引设置,查询设置
索引设置:不常用,因为随着业务的改变,要设置权重的域也会发生改变,而索引一旦创建则无法修改,除非删除索引重建。
索引设置:
PUT hotel
{
"mappings": {
"properties": {
"name":{
"type": "text",
"analyzer": "ik_max_word",
"boost": 5
},
"address":{
"type": "text",
"analyzer": "ik_max_word",
"boost": 3
}
}
}
}
查询设置:
该方式在开发中很常用,根据业务条件需求,在查询时灵活的配置权重。
在下列查询中,query中的内容为主查询条件,functions中为判断要为哪些数据加权。weight为加权值。
GET hotel/_search
{
"query": {
"function_score": {
"query": {
"query_string": {
"fields": ["name","synopsis","area","address"],
"query": "北京市万豪spa三星"
}
},
"functions": [
{
"filter": {
"term": {
"isAd": "1"
}
},
"weight": 100
}
]
}
}
}
public Map<String, Object> searchScoreQuery(Integer current, Integer size, Map<String, Object> searchParam) {
//创建查询请求对象
SearchRequest searchRequest = new SearchRequest("hotel");
//创建SearchSourceBuilder,设置DSL
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(searchParam.get("condition").toString())
.field("name")
.field("synopsis")
.field("area")
.field("address")
.defaultOperator(Operator.OR);
//构建加权条件
FunctionScoreQueryBuilder.FilterFunctionBuilder[] scoreFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("isAd", 1), ScoreFunctionBuilders.weightFactorFunction(100))
};
FunctionScoreQueryBuilder queryBuilder = QueryBuilders.functionScoreQuery(queryStringQueryBuilder, scoreFunctionBuilders);
searchSourceBuilder.query(queryBuilder);
//设置分页
searchSourceBuilder.from((current-1)*size);
searchSourceBuilder.size(size);
searchRequest.source(searchSourceBuilder);
try {
//执行查询
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
SearchHits searchHits = searchResponse.getHits();
long totalHits = searchHits.getTotalHits().value;
SearchHit[] hits = searchHits.getHits();
List<HotelEntity> list = new ArrayList<>();
for (SearchHit hit : hits) {
String sourceAsString = hit.getSourceAsString();
list.add(JSON.parseObject(sourceAsString,HotelEntity.class));
}
Map<String, Object> map = new HashMap<>();
map.put("list", list);
map.put("totalResultSize", totalHits);
map.put("current", current);
//设置总页数
map.put("totalPage", (totalHits + size - 1) / size);
return map;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}