一、简介安装
官方网址:https://www.elastic.co/cn/
安装elasticsearch:下载并解压
安装kibana:下载并解压
下载ik分词器:https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v8.2.0
1、适配kibana中kibana.yml文件
elasticsearch.hosts: ["http://localhost:9200"]
2、解压ik分词器包放在elasticsearch的plugins的新建ik文件夹下
官方网址:https://www.elastic.co/cn/
3、操作网址:http://localhost:5601/app/dev_tools#/console
二、ik分词解析器
1、分词最少切分
POST /_analyze
{
"text": "河马生鲜,阿里巴巴出品",
"analyzer": "ik_smart"
}
2、分词最细切分
POST /_analyze
{
"text": "河马生鲜,阿里巴巴出品",
"analyzer": "ik_max_word"
}
3、扩展ik分词器
在IKAnalyzer.xml文件中设置扩展配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">ext.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">stopword.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
在ext.dic、stopword.dic文件中添加词汇
三、操作索引库
1、mapping常见属性
type:数据类型(是否参与搜索)
index:是否索引
analyzer:分词器
properties:子字段
2、创建索引库(spring索引库名称)
PUT /spring
{
"mappings": {
"properties": {
"info": {
"analyzer": "ik_smart",
"type": "text"
},
"email":{
"type": "keyword",
"index": false
},
"name":{
"type": "object",
"properties": {
"lastName": {
"type": "keyword"
}
}
}
}
}
}
3、查询索引库
GET /spring
4、删除索引库
DELETE /spring
5、修改索引库
PUT /spring/_mapping
{
"properties": {
"新字段名": {
"type": "keyword"
"index": false
}
}
}
四、操作文档
1、插入文档(spring索引库名称,1文档id)
POST /spring/_doc/1
{
"info": "文本内容分词",
"email": "123456@163.com",
"name": {
"lastName": "赵"
}
}
2、查询文档
GET /spring/_doc/1
3、删除文档
DELETE /spring/_doc/1
4、全量修改
PUT /spring/_doc/1
{
"info": "阿里巴巴出品,等于白嫖了,真的是奥利给",
"email": "ALIBB@COM",
"name": {
"lastName": "赵"
}
}
5、局部修改
POST /spring/_update/1
{
"doc": {
"email": "alibaba@ali.cn"
}
}
五、代码操作ES(RestClient)
1、官方文档地址
https://www.elastic.co/guide/en/elasticsearch/client/index.html
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html
2、mapping映射字符串
{
"mappings": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"analyzer": "ik_max_word"
},
"address": {
"type": "keyword",
"index": false
},
"price": {
"type": "integer"
},
"score": {
"type": "integer"
},
"brand": {
"type": "keyword",
"copy_to": "name"
},
"city": {
"type": "keyword"
},
"starName": {
"type": "keyword"
},
"business": {
"type": "keyword",
"copy_to": "name"
},
"location": {
"type": "geo_point"
},
"pic": {
"type": "keyword",
"index": false
}
}
}
}
3、在pom文件中引入
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
4、索引库操作
public class HotelIndexTest {
private RestHighLevelClient restHighLevelClient;
@Test
void createHotelIndex() throws IOException {
//创建Request对象
CreateIndexRequest request = new CreateIndexRequest("hotel");
//DSL语句
request.source(MAPPING_TEMPLATE, XContentType.JSON);
//发送请求
restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
}
//删除索引库
@Test
void deleteHotelIndex() throws IOException {
//创建Request对象
DeleteIndexRequest request = new DeleteIndexRequest("hotel");
//发送请求
restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
}
//索引库是否存在
@Test
void existsHotelIndex() throws IOException {
//创建Request对象
GetIndexRequest request = new GetIndexRequest("hotel");
//发送请求
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
}
@BeforeEach
void setUp() {
this.restHighLevelClient = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://localhost:9200")));
}
@AfterEach
void tearDown() throws IOException {
restHighLevelClient.close();
}
}
设置MAPPING_TEMPLATE常量
public class HotelConstants {
public static final String MAPPING_TEMPLATE = "{\n" +
" \"mappings\": {\n" +
" \"properties\": {\n" +
" \"id\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"name\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"address\": {\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"price\": {\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"score\": {\n" +
" \"type\": \"integer\"\n" +
" },\n" +
" \"brand\": {\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"city\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"starName\": {\n" +
" \"type\": \"keyword\"\n" +
" },\n" +
" \"business\": {\n" +
" \"type\": \"keyword\",\n" +
" \"copy_to\": \"all\"\n" +
" },\n" +
" \"location\": {\n" +
" \"type\": \"geo_point\"\n" +
" },\n" +
" \"pic\": {\n" +
" \"type\": \"keyword\",\n" +
" \"index\": false\n" +
" },\n" +
" \"all\": {\n" +
" \"type\": \"text\",\n" +
" \"analyzer\": \"ik_max_word\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
}
5、文档操作
@SpringBootTest
public class HotelDocumentTest {
@Autowired
private IHotelService hotelService;
private RestHighLevelClient client;
//插入文档数据
@Test
public void testAddDocument() throws IOException {
//根据ID查询酒店数据
Hotel hotel = hotelService.getById(38665L);
//转换为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel);
//创建Request对象
IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
//准备json文档
request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
//发送请求
client.index(request, RequestOptions.DEFAULT);
}
//获取文档数据
@Test
public void testGetDocumentById() throws IOException {
//创建Request对象
GetRequest request = new GetRequest("hotel", "61083");
//发送请求
GetResponse documentFields = client.get(request, RequestOptions.DEFAULT);
String sourceAsString = documentFields.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(sourceAsString, HotelDoc.class);
}
//更新文档
@Test
public void testUpdateDocumentById() throws IOException {
//创建Request对象
UpdateRequest request = new UpdateRequest("hotel", "61083");
request.doc("price",888);
//发送请求
client.update(request, RequestOptions.DEFAULT);
}
//删除文档
@Test
public void testDeleteDocumentById() throws IOException {
//创建Request对象
DeleteRequest request = new DeleteRequest("hotel", "61083");
//发送请求
client.delete(request, RequestOptions.DEFAULT);
}
//批量导入
@Test
public void testBulk() throws IOException {
//根据ID查询酒店数据
List<Hotel> list = hotelService.list();
//创建Request对象
BulkRequest request = new BulkRequest();
for (Hotel hotel : list) {
//转换为文档类型
HotelDoc hotelDoc = new HotelDoc(hotel);
request.add(new IndexRequest("hotel").id(hotel.getId().toString()).source(JSON.toJSONString(hotelDoc), XContentType.JSON));
}
//发送请求
client.bulk(request, RequestOptions.DEFAULT);
}
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://localhost:9200")));
}
@AfterEach
void tearDown() throws IOException {
client.close();
}
}
六、DSL查询语句
查询所有:查询出所有数据,一般测试用
match_all
全文检索:利用分词器对用户输入内容分词,然后去倒排索引库中匹配
match_query、multi_match_query
精准查询:根据精确词条值查询数据,一般查询keyword、数值、日期、boolean等
ids、range、term
地理查询:根据经纬度查询
geo_distance、geo_bounding_box
复合查询:复合查询可以将上述各种查询条件组合起来,合并查询条件
bool、function_score
1、查询所有
GET /hotel/_search
{
"query": {
"match_all": {
}
}
}
#全文查询
GET /hotel/_search
{
"query": {
"match": {
"name": "地区"
}
}
}
GET /hotel/_search
{
"query": {
"multi_match": {
"query": "外滩如家",
"fields": ["brand","name","business"]
}
}
}
2、精确查询
#根据词条精确查询
GET /hotel/_search
{
"query": {
"term": {
"city": {
"value": "北京"
}
}
}
}
3、根据值的范围查询
GET /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 100,
"lte": 300
}
}
}
}
4、地理查询
GET /hotel/_search
{
"query": {
"geo_bounding_box": {
"location": {
"top_left": {
"lat": 40.73,
"lon": -74.1
},
"bottom_right": {
"lat": 40.717,
"lon": -73.99
}
}
}
}
}
GET /hotel/_search
{
"query": {
"geo_distance": {
"distance": "3km",
"location": {
"lat": 31.21,
"lon": 121.5
}
}
}
}
5、复合查询
GET /hotel/_search
{
"query": {
"function_score": {
"query": {
"match": {
"name": "上海外滩"
}
},
"functions": [
{
"filter": {
"term": {
"brand": "如家"
}
},
"weight": 10
}
],
"boost_mode": "multiply"
}
}
}
6、组合查询
#must:必须匹配每个子查询,类似“与”
#should:选择性匹配子查询,类似“或”
#must_not:必须不匹配,不参与算分,类似“非”
#filter:必须匹配,不参与算分
GET /hotel/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"city": {
"value": "上海"
}
}
}
],
"should": [
{
"term": {
"brand": {
"value": "皇冠假日"
}
}
},
{
"term": {
"brand": {
"value": "华美达"
}
}
}
],
"must_not": [
{
"range": {
"price": {
"lte": 500
}
}
}
],
"filter": [
{
"range": {
"score": {
"gte": 45
}
}
}
]
}
}
}
7、排序
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"score": {
"order": "desc"
}
},
{
"price": {
"order": "asc"
}
}
]
}
GET /hotel/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 31.034661,
"lon": 121.612282
},
"order": "asc",
"unit": "km"
}
}
]
}
八、代码实现查询语句
public class HotelSearchTest {
private RestHighLevelClient client;
//查询所有
@Test
void searchMatchAll() throws IOException {
MatchAllQueryBuilder query = QueryBuilders.matchAllQuery();
search(query);
}
//全文查询
@Test
void searchMatch() throws IOException {
MatchQueryBuilder query = QueryBuilders.matchQuery("name", "上海外滩");
search(query);
}
@Test
void searchMultiMatch() throws IOException {
MultiMatchQueryBuilder query = QueryBuilders.multiMatchQuery("北京", "name", "brand");
search(query);
}
//精准查询
@Test
void searchTerm() throws IOException {
TermQueryBuilder query = QueryBuilders.termQuery("city", "深圳");
search(query);
}
@Test
void searchRange() throws IOException {
RangeQueryBuilder query = QueryBuilders.rangeQuery("price").gte(100).lte(500);
search(query);
}
/**
* must:必须匹配每个子查询,类似“与”
* should:选择性匹配子查询,类似“或”
* must_not:必须不匹配,不参与算分,类似“非”
* filter:必须匹配,不参与算分
*/
//组合查询
@Test
void searchBool() throws IOException {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.termQuery("city", "上海"));
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(500));
search(boolQuery);
}
//查询语句抽取
public void search(QueryBuilder queryBuilder) throws IOException {
//准备Request
SearchRequest request = new SearchRequest("hotel");
//准备DSL
request.source().query(queryBuilder);
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析响应
handleResponse(response);
}
//分页查询
@Test
void searchPageAndSort() throws IOException {
int page = 1;
//准备Request
SearchRequest request = new SearchRequest("hotel");
//准备DSL
request.source().query(QueryBuilders.matchAllQuery());
request.source().sort("price", SortOrder.ASC);
request.source().from((page - 1) * 20).size(20);
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析响应
handleResponse(response);
}
//高亮查询
@Test
void searchHighlight() throws IOException {
//准备Request
SearchRequest request = new SearchRequest("hotel");
//准备DSL
request.source().query(QueryBuilders.matchQuery("name", "如家"));
request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析响应
handleResponse(response);
}
//聚合查询
@Test
void searchAggregation() throws IOException {
//准备Request
SearchRequest request = new SearchRequest("hotel");
//准备DSL
request.source().size(0);
request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(20));
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析响应
System.out.println(response);
//解析聚合结果
Aggregations aggregations = response.getAggregations();
//根据名称获取聚合结果
Terms brandAgg = aggregations.get("brandAgg");
//获取桶
List<? extends Terms.Bucket> buckets = brandAgg.getBuckets();
//遍历
for (Terms.Bucket bucket :buckets){
String brandName = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
System.out.println(brandName + docCount);
}
}
//解析json抽取
public void handleResponse(SearchResponse response) {
SearchHits searchHits = response.getHits();
long total = searchHits.getTotalHits().value;
System.out.println("共搜索到" + total + "条");
SearchHit[] hits = searchHits.getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//获取高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(highlightFields)) {
HighlightField highlightField = highlightFields.get("name");
if (highlightField != null) {
String name = highlightField.getFragments()[0].string();
hotelDoc.setName(name);
}
}
System.out.println(hotelDoc);
}
}
@BeforeEach
void setUp() {
this.client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://localhost:9200")));
}
@AfterEach
void tearDown() throws IOException {
client.close();
}
}
九、实例练习
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
@Autowired
private RestHighLevelClient client;
@Override
public PageResult search(RequestParams params) throws IOException {
//准备Request
SearchRequest request = new SearchRequest("hotel");
//Query查询
buildBasicQuery(request, params);
//分页
if (params.getPage() != null && params.getSize() != null)
request.source().from(0).size(5);
else
request.source().from((params.getPage() - 1) * params.getSize()).size(params.getSize());
//距离排序
if (params.getLocation() != null && !params.getLocation().equals(""))
request.source().sort(SortBuilders.
geoDistanceSort("location", new GeoPoint(params.getLocation())).
order(SortOrder.ASC).
unit(DistanceUnit.KILOMETERS));
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
return handleResponse(response);
}
private void buildBasicQuery(SearchRequest request, RequestParams params) {
//1、组合查询
BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
//输入框内容
if (params.getKey() == null || params.getKey().equals(""))
queryBuilder.must(QueryBuilders.matchAllQuery());
else
queryBuilder.must(QueryBuilders.matchQuery("name", params.getKey()));
//地区
if (params.getCity() != null && !params.getCity().equals(""))
queryBuilder.filter(QueryBuilders.termQuery("city", params.getCity()));
//品牌
if (params.getBrand() != null && !params.getBrand().equals(""))
queryBuilder.filter(QueryBuilders.termQuery("brand", params.getBrand()));
//星级
if (params.getStarName() != null && !params.getStarName().equals(""))
queryBuilder.filter(QueryBuilders.termQuery("starName", params.getStarName()));
//价格查询
if (params.getMinPrice() != null && params.getMaxPrice() != null)
queryBuilder.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
//2、算分控制
FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
//原始查询
queryBuilder,
//function数组
new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
//其中一个function元素
new FunctionScoreQueryBuilder.FilterFunctionBuilder(
//过滤条件
QueryBuilders.termQuery("isAD", true),
//算分函数
ScoreFunctionBuilders.weightFactorFunction(10)
)
});
//将条件查询封装
request.source().query(functionScoreQuery);
}
//解析json抽取
public PageResult handleResponse(SearchResponse response) {
PageResult pageResult = new PageResult();
//解析响应
SearchHits searchHits = response.getHits();
//获取总条数
long total = searchHits.getTotalHits().value;
pageResult.setTotal(total);
//获取数据集合
SearchHit[] hits = searchHits.getHits();
List<HotelDoc> list = new ArrayList<>();
for (SearchHit hit : hits) {
//获取单个数据
String json = hit.getSourceAsString();
//反序列化
HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
//获取排序值,距离
Object[] sortValues = hit.getSortValues();
if (sortValues != null && sortValues.length > 0) {
Object value = sortValues[0];
hotelDoc.setDistance(value);
}
//获取高亮结果
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
if (!CollectionUtils.isEmpty(highlightFields)) {
HighlightField highlightField = highlightFields.get("name");
if (highlightField != null) {
String name = highlightField.getFragments()[0].string();
hotelDoc.setName(name);
}
}
list.add(hotelDoc);
}
pageResult.setHotels(list);
return pageResult;
}
/**
* 聚合结果
*/
@Override
public Map<String, List<String>> filters(RequestParams params) throws IOException {
Map<String, List<String>> map = new HashMap<>();
//准备Request
SearchRequest request = new SearchRequest("hotel");
//准备DSL
//Query查询
buildBasicQuery(request, params);
//聚合查询
buildAggregation(request);
//发送请求
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//解析响应
System.out.println(response);
//解析聚合结果
Aggregations aggregations = response.getAggregations();
//根据名称获取聚合结果
List<String> cityList = getAggByName(aggregations, "cityAgg");
List<String> brandList = getAggByName(aggregations, "brandAgg");
List<String> starList = getAggByName(aggregations, "starAgg");
map.put("城市", cityList);
map.put("品牌", brandList);
map.put("星级", starList);
return map;
}
private void buildAggregation(SearchRequest request) {
request.source().size(0);
request.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(100));
request.source().aggregation(AggregationBuilders.terms("cityAgg").field("city").size(100));
request.source().aggregation(AggregationBuilders.terms("starAgg").field("starName").size(100));
}
public List<String> getAggByName(Aggregations aggregations, String agg) {
//聚合集合
List<String> list = new ArrayList<>();
//根据聚合名称获取聚合数据 m
Terms terms = aggregations.get(agg);
//获取聚合数据结果集
List<? extends Terms.Bucket> buckets = terms.getBuckets();
//遍历
for (Terms.Bucket bucket : buckets) {
String brandName = bucket.getKeyAsString();
list.add(brandName);
}
return list;
}
}