文章目录
一、简介
在Elasticsearch中,文档归属于一种类型(type),而这些类型存在于索引(index)中,类比传统关系型数据库:
Elasticsearch集群可以包含多个索引(indices)(数据库),每一个索引可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。
接入方式
使用spring-boot中的spring-data-elasticsearch,可以使用两种内置客户端接入:
1、节点客户端(node client):
配置文件中设置为local:false,节点客户端以无数据节点(node-master或node-client)身份加入集群,换言之,它自己不存储任何数据,但是它知道数据在集群中的具体位置,并且能够直接转发请求到对应的节点上。
2、传输客户端(Transport client):
配置文件中设置为local:true,这个更轻量的传输客户端能够发送请求到远程集群。它自己不加入集群,只是简单转发请求给集群中的节点。
二、项目依赖
代码中引入pom配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置文件中配置es的地址及账号密码
spring.elasticsearch.rest.uris=ip:port
spring.elasticsearch.rest.username=XX
spring.elasticsearch.rest.password=XX
三、对象中的注解
1.对象创建示例
@Document(indexName = "XX")
@Data
public class EsBillInfoEnd implements Serializable {
@Id
private String id;
@Field("enterName")
private String name;
private String payName;
}
2.对象中的注解
2.1 @Document注解
应用于类级别,表示该类是映射到数据库的候选.里面包含的重要属性:
indexName:存储该实体的索引名称。
createIndex: 标记是否在存储库引导时创建索引。默认值为true。Spring Data Elasticsearch 将在应用程序启动时引导存储库支持期间检查@Document注释定义的索引是否存在。如果它不存在,将创建索引,并将从实体的注释派生的映射写入新创建的索引。
versionType: 版本管理的配置。默认值为EXTERNAL。
2.2 @Id注解
应用于字段级别以标记用于标识目的的字段。
2.3 @Transient注解
默认情况下,所有字段在存储或检索时都映射到文档,此注释不包括该字段。
2.4 @PersistenceConstructor注解
标记给定的构造函数 – 即使是受包保护的构造函数 – 在从数据库实例化对象时使用。构造函数参数按名称映射到检索到的文档中的键值。
2.5 @Field注解
应用于字段级别,定义字段的属性,里面包含的重要属性:
name: 字段名称,如果未设置,则使用 Java 字段名称。
type:字段类型。
format:一种或多种内置日期格式。
pattern:一种或多种自定义日期格式。
store: 标志是否应将原始字段值存储在 Elasticsearch 中,默认值为false。
analyzer,searchAnalyzer,normalizer用于指定自定义分析器和规范器。
2.6 @GeoPoint注解
将字段标记为geo_point地理位置数据类型。如果字段是GeoPoint类的实例,则可以省略。
四、代码使用
Elasticsearch 模块支持所有基本查询构建功能,如字符串查询、本地搜索查询、基于条件的查询或从方法名称派生的查询,通过建造查询对象实现复杂查询。
上部分代码为Java使用代码,下部分代码为Elasticsearch json查询语句。
1.根据方法名称创建查询
关键词 | 描述 |
---|---|
find…By, read…By, get…By, query…By, search…By,stream…By | 通用查询方法通常返回存储库类型、Collection或Streamable子类型或结果包装器,例如Page,GeoResults或任何其他特定于商店的结果包装器。可用作findBy…,findMyDomainTypeBy…或与其他关键字结合使用。 |
exists…By | 通常返回boolean结果。 |
count…By | 计数返回数字结果。 |
delete…By, remove…By | 删除查询方法返回无结果 ( void) 或删除计数。 |
…First…, …Top… | 将查询结果限制为第一个结果。此关键字可以出现在主题的find(和其他关键字)和之间的任何位置by。 |
…Distinct… | 使用不同的查询仅返回唯一的结果。查阅特定于商店的文档是否支持该功能。此关键字可以出现在主题的find(和其他关键字)和之间的任何位置by。 |
interface BookRepository extends Repository<Book, String> {
List<Book> findByNameAndPrice(String name, Integer price);
}
{
"query": {
"bool" : {
"must" : [
{ "query_string" : { "query" : "?", "fields" : [ "name" ] } },
{ "query_string" : { "query" : "?", "fields" : [ "price" ] } }
]
}
}
}
2.使用JAVA API查询
- 首先建造一个查询对象(下文中的操作步骤都基于此查询对象)
NativeSearchQueryBuilder nativeSearchQuery = new NativeSearchQueryBuilder();
NativeSearchQueryBuilder :用于建造一个NativeSearchQuery查询对象
2.1 简单查询
常用的SQL运算符和聚合函数对应的ES Builder:
Sql element | Aggregation Type | Code to build |
---|---|---|
AND | BoolQueryBuilder | QueryBuilders.boolQuery().must().add(sub QueryBuilder) |
OR | BoolQueryBuilder | QueryBuilders.boolQuery().should().add(sub QueryBuilder) |
NOT | BoolQueryBuilder | QueryBuilders.boolQuery().mustNot().add(sub QueryBuilder) |
= | BoolQueryBuilder | QueryBuilders.termQuery(fieldName, value) |
IN | BoolQueryBuilder | QueryBuilders.termsQuery(fieldName, values) |
LIKE | BoolQueryBuilder | QueryBuilders.wildcardQuery(fieldName, value) |
> | BoolQueryBuilder | QueryBuilders.rangeQuery(fieldName).gt(value) |
>= | BoolQueryBuilder | QueryBuilders.rangeQuery(fieldName).gte(value) |
< | BoolQueryBuilder | QueryBuilders.rangeQuery(fieldName).lt(value) |
<= | BoolQueryBuilder | QueryBuilders.rangeQuery(fieldName).lte(value) |
QueryStringQuery 支持在查询字符串中通过 AND OR NOT 进行布尔运算,同时也支持 +(must) 和 -(must not),通过指定多个查询字段以及复杂的布尔运算,我们可以精确的获取文档数据。
查询时字段名加.keyword则为精确查询,不加则默认分词查找
2.1.1 字段包含XX(queryStringQuery termQuery boolQuery)
- match和term差别对比:
match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找,而term会直接对关键词进行查找。一般模糊查找的时候,多用match,而精确查找时可以使用term。
- bool查询包含四种操作符,分别是must,should,must_not,query。它们均是一种数组,数组里面是对应的判断条件:
must: 必须匹配,与and等价。贡献算分
must_not:必须不匹配,与not等价,常过滤子句用,但不贡献算分
should: 选择性匹配,至少满足一条,与 OR 等价。贡献算分
filter: 过滤子句,必须匹配,但不贡献算分
//查询enterName字段中包含台州医院的数据
nativeSearchQuery.withQuery(QueryBuilders.queryStringQuery("台州医院").defaultField("enterName"));
或者
nativeSearchQuery.withQuery(QueryBuilders.termQuery("enterName","台州医院"));
或者
nativeSearchQuery.withQuery(QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("enterName","台州医院")));
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"query_string": {
"query": "台州医院",
"fields": [
"enterName"
]
}
}
}
或者
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"term": {
"enterName": "台州医院"
}
}
}
或者
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"enterName": "台州医院"
}
}
]
}
}
}
2.1.2 多字段包含XX(queryStringQuery)
//查询enterName字段中包含 台州医院 或者payName字段中包含 台州医院 的数据(分词查询)
nativeSearchQuery.withQuery(QueryBuilders.queryStringQuery("台州医院").field("enterName").field("payName"));
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"query_string": {
"query": "台州医院",
"fields": [
"enterName",
"payName"
]
}
}
}
2.1.3 含有XX且不含有YY(queryStringQuery)
//查询enterName字段中含有医院且不含有台州的数据
nativeSearchQuery.withQuery(QueryBuilders.queryStringQuery("+医院 -台州").defaultField("enterName"));
2.1.4 含有XX或者不含有YY(simpleQueryStringQuery)
//查询enterName字段中含有医院或者不含有台州的数据
nativeSearchQuery.withQuery(QueryBuilders.simpleQueryStringQuery("+医院 -台州").field("enterName"));
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"query_string": {
"query": "(台州 or 医院)",
"fields": [
"enterName"
]
}
}
}
2.2 模糊查询
2.2.1 左右模糊查询(fuzzyQuery matchPhraseQuery)
//左右模糊查询,其中fuzziness的参数作用是在查询时,es动态的将查询关键词前后增加或者删除一个词,然后进行匹配
QueryBuilders.fuzzyQuery("enterName", "台州医院").fuzziness(Fuzziness.ONE)
或者
QueryBuilders.matchPhraseQuery("enterName","台州医院");
- 说明:
fuzzy和match_phrase的区别
1.fuzzy是词/项级别的模糊匹配,match_phrase是基于短语级别的
例如对于英文(standard分析器)来说”dog cat bird”来说”dog”就是一个词/词项,而”dog cat”就是一个短语,因此作用范围不一样
2.fuzzy是基于莱文斯坦距离的,所以fuzzy是可以容错的例如你输入”dcg” 你也可以匹配到”dog cat bird”,但是这里注意的是你的查询只能是单词条的查询,不能”dcg cat”,如果你需要查询短语里面的拼写错误,可以使用match的fuzziness参数,match_phrase是不允许出现不存在的词条的。
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"fuzzy": {
"enterName": {
"value": "台州医院",
"fuzziness": 1
}
}
}
}
或者
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"match_phrase": {
"enterName": "台州医院"
}
}
}
2.2.2 前缀查询(prefixQuery)
//前缀查询,查询title中以“台州医院”为前缀的document;
QueryBuilders.prefixQuery("enterName", "台州医院")
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"prefix": {
"enterName": {
"value": "台州医院"
}
}
}
}
2.2.3 通配符查询(wildcardQuery)
//通配符查询,支持*和?,?表示单个字符;注意不建议将通配符作为前缀,否则导致查询很慢
QueryBuilders.wildcardQuery("enterName", "台*院")
QueryBuilders.wildcardQuery("enterName", "台?院")
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"wildcard": {
"enterName": {
"value": "台*院"
}
}
}
}
2.3 范围查询
- 说明:
include_lower表示是否包含边界最小值(true表示包含),include_upper表示是否包含边界最大值(true表示包含,false表示不包含)
gte:大于或等于
gt: 大于
lte:小于或等于
lt:小于
其中 from to 与 gte lt 等查询无明显区别
当只有一个查询条件时,查询参数可直接放入query中,多条查询条件放入bool中
2.3.1 闭区间查询(rangeQuery)
//闭区间查询,查询2021-06-23之间的数据
nativeSearchQuery.withQuery(QueryBuilders.rangeQuery("@timestamp").from("2021-06-23T00:00:00Z").to("2021-06-23T23:59:59Z").includeLower(true).includeUpper(false));
或者
nativeSearchQuery.withQuery(QueryBuilders.boolQuery().must(QueryBuilders.rangeQuery("@timestamp").gte("2021-06-23T00:00:00Z").lt("2021-06-23T23:59:59Z").includeLower(true).includeUpper(false)));
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"range": {
"@timestamp": {
"from": "2021-06-23T00:00:00Z",
"to": "2021-06-23T23:59:59Z",
"include_lower": true,
"include_upper": false,
"boost": 1
}
}
}
}
或者
GET dc-elk-bill-info-end-2021.06/_search
{
"query": {
"bool": {
"must": [
{
"range": {
"@timestamp": {
"gte": "2021-06-23T00:00:00Z",
"lt": "2021-06-23T23:59:59Z",
"include_lower": true,
"include_upper": false,
"boost": 1
}
}
}
]
}
}
}
2.3.2 开区间查询\大于\大于等于\小于\小于等于
// 开区间查询,默认是true,也就是包含
QueryBuilders.rangeQuery("fieldName").from("fieldValue1").to("fieldValue2").includeUpper(false).includeLower(false);
//大于
QueryBuilders.rangeQuery("fieldName").gt("fieldValue");
//大于等于
QueryBuilders.rangeQuery("fieldName").gte("fieldValue");
//小于
QueryBuilders.rangeQuery("fieldName").lt("fieldValue");
//小于等于
QueryBuilders.rangeQuery("fieldName").lte("fieldValue");
2.3.3 分页(PageRequest)
nativeSearchQuery.withPageable(PageRequest.of(0, 5));
ES默认返回的条数值为10,如果想改变返回的条数值,可以指定size的大小,也可以指定from的起始位置,也就好比mysql中的pageIndex,size是pageSize,对应于mysql中的limit 0,5,则在ES中是from 0 to 5
GET dc-elk-bill-info-end-2021.06/_search
{
"from": 0,
"size": 5
}
2.3.4 排序(SortBuilders)
nativeSearchQuery.withSort(SortBuilders.fieldSort("@timestamp").order(SortOrder.ASC));
- sort表示排序,ES提供了三种排序方式:
①:按照文档的得分来排序,ES会自动根据查询的条件来匹配文档,每个文档命中值就会有一个score值,可以按照score值进行排序
②按照指定字段的值可以倒序,也可以正序③按照指定地理位置的距离来进行排序,这里需要注意的就是在排序字段上如果是text类型就必须开启 fielddata,而keyword可以直接用来排序,所以建议如果要给字段排序就最好声明为keyword类型。
GET dc-elk-bill-info-end-2021.06/_search
{
"sort": [
{
"logTime": {
"order": "desc"
}
}
]
}
2.3.5 经纬度查询(GeoDistanceQueryBuilder)
//以某点为中心,搜索指定范围
nativeSearchQuery.withQuery(QueryBuilders.boolQuery().filter(new GeoDistanceQueryBuilder("location").point(40.010955, 118.68545).distance(1, DistanceUnit.KILOMETERS)));
// 按距离升序
nativeSearchQuery.withSort(new GeoDistanceSortBuilder("location",40.010955, 118.68545).unit(DistanceUnit.KILOMETERS).order(SortOrder.ASC));
- 说明:
GeoDistanceQueryBuilder : 地理位置查询对象
point :中心点坐标
distance:距离 单位/km
location:坐标点 圆心所在位置
geo_distance :找出与指定位置在给定距离内的点
geo_polygon : 找出落在多边形中的点
geo_bounding_box : 找出落在指定矩形框中的坐标点
geo_distance_range :找出与指定点距离在给定最小距离和最大距离之间的点
2.4 聚合查询
- Aggregation -> SubAggregation
SubAggregation是在原来的Aggregation的计算结果中进一步做聚合计算
Elasticsearch 默认对于分词的字段(text类型)不支持聚合
- 返回数据字段:
name: 和请求中的Aggregation的名字对应
buckets: 每个Bucket对应Agggregation结果中每一个可能的取值和相应的聚合结果.
Bucket 属性:
key: 对应的是聚合维度可能的取值, 具体的值和Aggregation的类型有关, 比如Term aggregation (按交易类型计算总金额), 那么Bucket key值就是所有可能的交易类型 (credit/debit etc). 又比如DateHistogram aggregation (按天计算交易笔数), 那么Bucket key值就是具体的日期.
docCount: 对应的是每个桶中的文本数量.
value: 对应的是聚合指标的计算结果. 注意如果是多层Aggregation计算, 中间层的Aggregation value一般没有值, 比如Term aggregation. 只有到底层具体计算指标的Aggregation才有值.
aggregations: 对应请求中当前Aggregation的subAggregation的计算结果 (如果存在)
- 常用的SQL运算符和聚合函数对应的ES Builder:
释义 | Sql element | Aggregation Type | Code to build |
---|---|---|---|
统计数量 | count(field) | ValueCountAggregationBuilder | AggregationBuilders.count(metricsName).field(fieldName) |
去重再统计数量 | count(distinct field) | CardinalityAggregationBuilder | AggregationBuilders.cardinality(metricsName).field(fieldName) |
求和 | sum(field) | SumAggregationBuilder | AggregationBuilders.sum(metricsName).field(fieldName) |
最小值 | min(field) | MinAggregationBuilder | AggregationBuilders.min(metricsName).field(fieldName) |
最大值 | max(field) | MaxAggregationBuilder | AggregationBuilders.max(metricsName).field(fieldName) |
平均值 | avg(field) | AvgAggregationBuilder | AggregationBuilders.avg(metricsName).field(fieldName) |
2.4.1 获取平均值(AvgAggregationBuilder)
//获取平均值
AvgAggregationBuilder avg = AggregationBuilders.avg("avg_of_field").field("resultTime");
builder.addAggregation(avg);
GET dc-elk-bill-info-end-2021.06/_search
{
"aggs": {
"terms_by_field": {
"avg": {
"field": "resultTime"
}
}
}
}
2.5 复杂查询
2.5.1 聚合后求值及统计数据(AggregationBuilders.sum)
//根据region字段聚合后totalAmount的总值及payNameCode的个数
TermsAggregationBuilder terms = AggregationBuilders.terms("terms_by_field")
.field("region.keyword")
.minDocCount(1)
.size(20)
.subAggregation(AggregationBuilders
.sum("sum_of_field")
.field("totalAmount"))
.subAggregation(AggregationBuilders
.cardinality("count_field_distinct")
.field("payNameCode.keyword"));
nativeSearchQuery.addAggregation(terms);
- TermsAggregationBuilder : 聚合查询对象
- AggregationBuilders.terms : 相当于sql中的group by
- terms_by_field : terms聚合函数结果名
- terms(“”).field : 匹配对应的字段名
- minDocCount : 返回最小的文档数。强制返回空数据。如果是0,时间间隔内缺少数据,则自动补充0.一般场景就是返回空数据,减少程序的处理。(通过设置min_doc_count和shard_min_doc_count来规定最小的文档数目,只有满足这个参数要求的个数的词条才会被记录返回)
- size : 返回的条数设置
- subAggregation : 添加子聚合
- sum_of_field : sum聚合函数结果名
- AggregationBuilders.cardinality : 类似sql中 count(distinct),先去重再求和
- count_field_distinct : cardinality函数结果名
GET dc-elk-bill-info-end-2021.06/_search
{
"aggs": {
"terms_by_field": {
"terms": {
"field": "region.keyword",
"size": 20,
"min_doc_count": 1
},
"aggregations": {
"sum_of_field": {
"sum": {
"field": "totalAmount"
}
},
"count_field_distinct": {
"cardinality": {
"field": "payNameCode.keyword"
}
}
}
}
}
}
2.5.2 每分钟各个执收单位的开票数据(dateHistogram)
// 每分钟各个执收单位的开票数据
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateHistogramAggregationBuilder dateHistogram = AggregationBuilders
.dateHistogram("count_by_time")
.field("@timestamp")
.fixedInterval(DateHistogramInterval.minutes(1))
.format("yyyy-MM-dd HH:mm:ss")
.order(BucketOrder.key(false))
.minDocCount(0)
.extendedBounds(new ExtendedBounds(formatter.format("2021-06-25 00:00:00"), formatter.format("2021-06-25 23:59:59")))
.subAggregation(AggregationBuilders.terms("terms_by_field")
.field("instance_id.keyword")
.minDocCount(1)
.size(200));
- 说明:
date_histgram 比普通的 histogram 支持更多的时间特性,可以灵活选择时间单位和支持 date math,比如时区的灵活变化
- AggregationBuilders.dateHistogram(“”) : 按照时间来构建集合(桶)Buckts的,当我们需要按照时间进行做一些数据统计的时候,就可以使用它来进行时间维度上构建指标分析,基于文档中的某个【日期类型】字段,,以【日期间隔】来桶分聚合。
- count_by_time : dateHistogram函数名称
- fixedInterval :指定间隔(又称桶大小)来匹配数据
- DateHistogramInterval.minutes() : 声明时间范围
- format : 规范返回时间格式
- order : 排序
- BucketOrder.key():按key的升序或降序排序
- minDocCount :返回最小的文档数。强制返回空数据。如果是0,时间间隔内缺少数据,则自动补充0.一般场景就是返回空数据,减少程序的处理。(通过设置min_doc_count和shard_min_doc_count来规定最小的文档数目,只有满足这个参数要求的个数的词条才会被记录返回)
- extendedBounds:此值只有当min_doc_count 为0时才具有意义。此值与min_doc_count 一起使用,是强制返回空数据。
GET dc-elk-bill-info-end-2021.06/_search
{
"aggs": {
"count_by_time": {
"date_histogram": {
"field": "@timestamp",
"interval": "minute",
"format": "yyyy-MM-dd HH:mm:ss",
"order": {
"_key": "asc"
},
"min_doc_count": 0,
"extended_bounds": {
"min": "2021-06-25 00:00:00",
"max": "2021-06-25 23:59:59"
}
},
"aggs": {
"terms_by_field": {
"terms": {
"field": "instance_id.keyword",
"size": 200,
"min_doc_count": 1
}
}
}
}
}
}
2.5.3 统计不同票据类别各个时间段的数据(AggregationBuilders.range)
// 统计不同票据类别各个时间段的数据
TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("terms_by_field")
.field("invAttribute.keyword")
.minDocCount(0)
.subAggregation(AggregationBuilders.range("count_field_range")
.field("resultTime")
.addUnboundedTo(30)
.addRange(30, 60)
.addRange(60, 180)
.addUnboundedFrom(180));
- addUnboundedTo(30) :统计大于三十的数据
- addRange(30, 60) : 统计大于等于三十并且小于60的数据
- addUnboundedFrom(180):统计大于180的数据
GET dc-elk-bill-info-end-2021.06/_search
{
"aggs": {
"terms_by_field": {
"terms": {
"field": "invAttribute.keyword",
"size": 10,
"min_doc_count": 10
},
"aggs": {
"count_field_range": {
"range": {
"field": "resultTime",
"ranges": [
{
"to": 30
},
{
"from": 30,
"to": 60
},
{
"from": 60,
"to": 180
},
{
"from": 180
}
]
}
}
}
}
}
}
- 查询条件构造完成
elasticsearchRestTemplate.search(query, EsBillInfoEnd.class);
- 或对象中未声明索引名称,代码中声明:
elasticsearchRestTemplate.search(query, EsBillInfoEnd.class, IndexCoordinates.of(indexName));
3.查询完成返回数据处理
SearchHits为es查询返回统一封装对象,如果要获取返回对象则需根据定义的函数名获取出对象数据
- 示例,如果返回数据为:
{
"took": 10,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 10000,
"relation": "gte"
},
"max_score": null,
"hits": [
]
},
"aggregations": {
"terms_by_field": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "330601",
"doc_count": 53337,
"count_field_range": {
"buckets": [
{
"key": "*-30.0",
"to": 30.0,
"doc_count": 15332
},
{
"key": "30.0-60.0",
"from": 30.0,
"to": 60.0,
"doc_count": 25883
},
{
"key": "60.0-180.0",
"from": 60.0,
"to": 180.0,
"doc_count": 11952
},
{
"key": "180.0-*",
"from": 180.0,
"doc_count": 170
}
]
}
}
]
}
}
}
则循环返回数据代码为:
SearchHits<EsBillInfoEnd> articleEntities = elasticsearchRestTemplate.search(nativeSearchQuery.build(), EsBillInfoEnd.class);
//terms_by_field : 查询时定义的函数名
ParsedTerms terms = articleEntities.getAggregations().get("terms_by_field");
for (Terms.Bucket bucket : terms.getBuckets()) {
String billType = bucket.getKeyAsString();
//二层函数则再进行循环
}