SpringBoot整合Elasticsearch

基础环境

  • SpringBoot 版本 : 2.4.0
  • ES 版本: 7.9.3
  • Kibana版本: 7.9.3
  • SpringBoot内置Tomcat版本: 9.0.39
  • spring-boot-starter-data-elasticsearch 版本: 对应springboot版本即可
    首先说明下版本对应的重要性和关联,在进行SpringBoot和ES整合的时候,版本的对应关系尤其重要,一般情况下,建议在SpringBoot官网上找到springboot中对应的spring-boot-starter-data-elasticsearch 版本,根据版本号判断支持的ES的版本;其次根据spring-data-es 的版本找到对应的springboot版本,需要在marven仓库中确认该springboot版本是否支持对应的ES版本,很有可能出现marven仓库中的版本对应关系和文档中有一定的出入。在springboot官网对应的es文档中,对应的关系如下:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xA7M1hai-1607665361839)(en-resource://database/1029:1)]
    参考链接: SpringBoot-Data-ES文档
    需要的springboot版本是 2.3.x,但实际在marven仓库中,2.3.x支持的版本是 6.8.x版本的es,而目前es最高版本已经升级至7.10 ,实际情况就是可能出现一定的兼容性问题,查看spring-boot-starter 2.4.0 版本,可以看到支持的es的版本为7.9.3,为当前次新的版本。综合来说,选择springboot 2.4.0 整合es 7.9.3 版本。
    其次直接选择SpringBoot 2.4.0 对于其内置的tomcat版本也有一定的需求,在marven仓库中也有标注,需要将内置tomcat内置版本升级至9.0.39,在此基础上,进行配置文件的配置,可以启动项目

配置文件

相对而言,springboot整合es对于配置文件中需要配置的信息是比较少的,目前刚开始的情况下,我本地项目中仅适用了两个配置,具体如下:spring.elasticsearch.rest.uris=http://127.0.0.1:9200
spring.data.elasticsearch.repositories.enabled = true
在Springboot-es 7.9.x 的版本整合中,已经放弃了原本5.6.1至今的节点名称配置,也逐步放弃适用TransportClient的API方式,改为Rest方式API进行调用,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L5LEZ9xq-1607665361845)(en-resource://database/1031:1)]

在配置文件中如果进行上面图片中配置,会有一定的报错或提示Deprecated,为了比较好的兼容,直接使用rest.uris 配置相对而言更合适,并且在开发中进行复杂查询时可以直接使用**ElasticsearchRestTemplate**。

实体类

    在ES 7.0之前的版本,ES中有 索引(index)、类型(type)、文档(doc)、字段(field) ,分别对应常规的关系型数据库中的 数据库、表、行、列,但如果整合的ES的版本为7.0及以上版本的时候,是移除了type的概念,从ES的官方解释来说,ES初期设计是参考关系型数据库的设计模式,所以存在了type(数据表)的概念,但ES本身是基于Luncene,根本快的原因是因为 倒序索引 的使用,而倒序索引 是基于index的,而非type,多个type反而会降低es搜索的速度,所以逐步过渡的情况下,es在7.x版本中去除了type。另外一种原因是因为type一定程度上限制了es的使用,比如两个实体类 User 和 WechatUser  其中都有 age 这个字段,但在User中字段类型是 int ,但在WechatUser中类型是 double ,在7.x之前的版本中,必须要求 User 和 WechatUser 的 type中 age这个字段的类型必须完全一致,但 7.x 的版本中,es所有不同类型的同字段内部使用的是同一个字段存储,区分type反而会降低es 压缩数据的能力。

在建立Bean的时候,需要在类头部添加@Document(index="xxx")的注解,并且在对应字段上添加@Field字段,可以根据实际使用情况添加是否使用分词器(分词器的作用后续再说),es 中比较特殊的是日期类型的格式,比较建议使用自定义注解的方式去定义,es默认会使用时间戳的方式进行保存。自定义时间格式的注解为:
@Field(type = FieldType.Date,format = DateFormat.custom,pattern = "yyyy-MM-dd HH:mm:ss")private Date invoiceDate;

基础CRUD

    Java中对于ES数据的查询主要分为两种,一个是继承 Repository<T, ID> ,实现一部分简单的CRUD查询,比较类似于mybatis-plus中的查询。Repository 的结构如下: PagingAndSortingRepository 继承 CrudRepository,继承  Repository,一般情况我们实际使用过程中 继承 PagingAndSortingRepository,基本可以实现简单的单个保存、批量保存、根据id删除等。另外一种方式是复杂查询(这里仅仅指组合查询),通过ElasticsearchRestTemplate 进行自定义数据检索操作。
Repository使用

新建一个Service直接继承PagingAndSortingRepository进行查询。

    public interface UserRepository extends PagingAndSortingRepository<User,Long> {}

继承Repository后,可以直接在Service层进行注入使用,

@Autowiredprivate UserRepository userRepository;
@Testvoid saveUser(){    Iterable<User> all = userRepository.saveAll(null);
}

userRepository.xxx方法基本实现了能够对实体类进行基本CRUD操作,但还有一些简单查询,但并不是已经默认定义好的,也可以通过一些关键字来实现。简单展示如下:

public interface InvoiceRepository extends
PagingAndSortingRepository<Invoice,Long> {
List<Invoice> findByInvoiceNum(String invoiceNum);
long deleteByInvoiceCodeAndInvoiceNumAndCompanyId(String invoiceCode,String invoiceNum,String companyId);}

两条自定义的处理语句作用为1:根据发票号码查询数据,2: 根据发票代码、号码、公司ID 删除发票数据,其中组建自定义语句的主要的关键字有 And、Or、Is、Not、Between、Like、After等,具体参考链接:自定义Repository关键字 ,参考文件篇幅为8.2.2 Query creation。
同样的定义语句也可以使用@Query(“es原生查询语句”)的方式进行编写,具体如下:

@Query("{ \"query\" : { \"bool\" : { \"must\" : [ { \"query_string\" : {
\"query\" : \"?\", \"fields\" : [ \"invoiceCode\" ] } } ] } }}")long findByInvoiceCode(String invoiceCode);
原生语法DSL
索引结构
  • 创建索引
    索引的创建在默认情况下分片数量是5个,副本数量是1个,但可以在创建的时候显式指定分片数量以及副本数量,具体代码如下,创建3个分片,2个副本的的索引
PUT fatp_sale_invoice
{"settings": {"number_of_shards": 3,"number_of_replicas": 2}
}# 返回
{"acknowledged" : true,"shards_acknowledged" : true,"index" : "fatp_test1"
}
  • 获取索引
GET fatp_test
# 返回
{"fatp_test" : {"aliases" : { },"mappings" : { },"settings" : {"index" : {"creation_date" : "1607563246081","number_of_shards" : "3","number_of_replicas" : "2","uuid" : "44UK8efkTRWFqzakTYTSxQ","version" : {"created" : "7090399"},"provided_name" : "fatp_test"}}}
}
  • 删除索引
    删除索引这件事最好还是谨慎点,这操作基本等同于删库了,一般情况下是不建议开发人员有操作删除索引这个权限的。
DELETE fatp_test# 返回
{"acknowledged" : true
}
  • 重建索引
    重建索引最基本的功能是拷贝文件从一个索引到另一个索引,语法如下:
POST _reindex
{"source": {"index": "fatp_sale_invoice"},"dest": {"index":"fatp_sale_invoice_bk"}
}# 返回
{"took" : 19009,"timed_out" : false,"total" : 23425,"updated" : 0,"created" : 23425,"deleted" : 0,"batches" : 24,"version_conflicts" : 0,"noops" : 0,"retries" : {"bulk" : 0,"search" : 0},"throttled_millis" : 0,"requests_per_second" : -1.0,"throttled_until_millis" : 0,"failures" : [ ]
}

参数说明:
took : 耗时毫秒数,
timed_out : 是否超时,
total : 总数据量,
updated : 更新数据量,
created : 创建数据量,
batches : 重重建索引拉回的滚动响应的数量

在此基础上,重建索引不仅仅可以全量复制,也可以添加一定的查询条件,限定值复制符合条件的数据,如下:

POST _reindex
{"source": {"index": "fatp_sale_invoice","query": {"match_phrase": {"invoiceNum": "112233"}}},"dest": {"index":"fatp_sale_invoice_bk"}
}
  • 字段映射类型
    es 中对于每个字段都有自己单独的类型,并且同一个字段同时拥有不同的类型,这个实现主要基于7.x 更新后更加明显的体现出,具体不多赘述,在之前的 实体类 介绍中已进行过一部分说明,字段的类型主要有 text、integer、date、double等,基本能够满足所有的字段类型的要求,一般有两种方式进行创建索引,一个是通过es原生语法创建时指定类型,另外是通过java等语言进行整合实体类的创建,保存数据的时候会自动创建,但如果使用代码进行创建索引,未指定具体字段类型的话,直接保存数据,es会默认读取某字段第一次保存时的值的类似进行判断获取字段值类型,后续如果值和当前值类型不匹配的时候,就会无法保存,所以建议通过代码创建索引的时候,一开始明确指定字段的具体类型。需要注意的是,在es中如果需要对某个字段进行排序或分组,需要设置字段的fielddata为true
  • 创建、更新映射
    创建和更新索引都是使用PUT方法,第一个参数为索引名称,第二个参数为更新索引映射,也就是字段设置,简单创建一个索引以及一个字段的语法如下,其中 “fielddata”:true ,名称为:字段数据,在使用 排序、聚合以及脚本访问中需要使用到的字段必须设置fielddata:true,缺点在于使用fielddata的字段会占据比较大的的堆内存空间,需要谨慎设置
PUT fatp_sale_invoice/_mapping
{"properties":{"invoiceLine":{"type":"text","fielddata":true}}
}
  • 查询索引字段映射
# 查询某索引下所有字段映射
GET fatp_sale_invoice/_mapping/
# 返回结果
{"fatp_sale_invoice" : {"mappings" : {"properties" : {"_class" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"invoiceDate" : {"type" : "date","format" : "date_hour_minute_second"},"billingMethodName" : {"type" : "text"},"totalAmount" : {"type" : "double"},"teamId" : {"type" : "integer"},"buyerName" : {"type" : "text"}}}}
} # 查询某索引下具体字段类型映射
GET fatp_sale_invoice/_mapping/field/invoiceNum
# 返回结果
{"fatp_sale_invoice" : {"mappings" : {"invoiceNum" : {"full_name" : "invoiceNum","mapping" : {"invoiceNum" : {"type" : "text","fielddata" : true}}}}}
}
查询过滤 query/filter
  • 在ES中查询和过滤有两种方式,一种是过滤、一种是查询,过滤主要使用关键字 filter,查询主要根据query中三个关键字 must/must not/should,两者的区别主要在于 filter 不参与评分,缓存数据,query 计算评分,不缓存数据,相对而言,很多时候filter会比queyr快。当然实际使用过程中,must/must not /should 等一般都是和 filter结合使用,简单举个栗子:
GET fatp_sale_invoice/_search
{"query": {"bool": {"must": [{"match_phrase": {"companyId": "91310230674560485R"}},{"match_phrase": {"invoiceStatus": "1"}}],"must_not": [{"match_phrase": {"isCancel": "1"}}], "filter": [{"range": {"invoiceDate": {"gte": "2020-01-01T00:00:00","lte": "2020-01-31T23:59:52"}}}]}},"sort": [{"invoiceNum": {"order": "desc"}}],"size": 10, "track_total_hits":true
}
  • 上述这段代码的主要实现的功能就是,通过几个must 条件,判断某些字段必须满足部分条件,某些字段必须不等于某些条件,最后通过时间范围进行数据过滤,对上述代码中的结构和作用进行简单解释下:
    1、curl 中的 GET 方法代表查询, fatp_sale_invoice 代表索引名称, _search 代表该语句时为了查询
    2、外层结构 query ,代表整个查询体,其中内部可以定义很多查询语句,这里定义了bool类型,因为需要对多个字段进行条件限定和条件否定,当然 query 结构体中也可以直接使用 match_phrase 等查询语法进行操作,但同样的查询语法关键字只能出现一次。
    3、match_phrase 短语匹配,这个内容会在后续进行详细解释,match_phrase 意义为查询的关键字中的某个字 一段 内容能够和查询的值匹配就会查询出,有点类似于 sql 中 like ‘%xx%’ 的意。
    4、filter 过滤,对查询出结果,根据时间范围进行过滤
    5、range 范围过滤关键字 ,gte 大于等于,lte 小于等于
    6、sort 排序 ,可以对查询结果根据某字段进行升序、降序排序
    7、size 查询展示数量,不写该属性的情况下,kibana 默认展示10条数据
    8、track_total_hits 返回命中结果是否全部显示 ,默认false 。在kibana中如果不设定该值,当返回结果总数大于10000的时候也会仅显示10000

    上述查询结果返回值如下:

{"took" : 6,"timed_out" : false,"_shards" : {"total" : 6,"successful" : 6,"skipped" : 0,"failed" : 0},"hits" : {"total" : {"value" : 170,"relation" : "eq"},"max_score" : 5.207824E-4,"hits" : [{"_index" : "fatp_sale_invoice","_type" : "_doc","_id" : "10444391","_score" : 5.207824E-4,"_source" : {"_class" : "com.aisino.fatp.entity.Invoice","id" : 10444391,"orderId" : "7e4725b3d9499897b","companyId" : "91310230674560485R","userId" : "618","invType" : "s","invoiceCode" : "310423422130","invoiceNum" : "51923230","invoiceDate" : "2020-01-14T06:56:28","invoiceStatus" : "1","buyerName" : "xxx媒有限公司","buyerTaxNo" : "9xxxxx7507","buyerAddrTel" : "上海市xxx15号16幢021-888888880","buyerBankAccount" : "xxxx路支行975323232323740000451","saleserName" : "上海xxxx有限公司","saleserTaxNo" : "91310sfasassa0485R","saleserAddrTel" : "xxxxxxx楼5211111111666","saleserBankAccount" : "上海xxx支行31222222200759391","totalAmount" : 1980.0,"noTaxAmount" : 1867.92,"taxAmount" : 112.08,"isCancel" : "0","clerk" : "1111xxx","invoiceSource" : "1","invoiceLine" : "1","machineNo" : "5","listType" : "0","taxType" : "1","departmentId" : 28,"employeeId" : 14,"employeeName" : "张三","teamId" : 33,"teamName" : "软件市场部-渠道销售一组","goldTaxNo" : "661232323814491","invoiceImgStatus" : 0,"invoiceDetails" : [{"goodsName" : "服务费","goodsId" : 111,"detailTaxFee" : 6,"detailTotalAmount" : 106,"detailNoTaxAmount" : 100,"taxRate" : 0.06,"goodsQuantity" : 1.0,"goodsPrice" : 100,"taxClassCode" : "3040201030000000000","taxShortName" : "信息技术服务","goodsLineType" : "0","taxPreFlag" : "0","lineNo" : 1}]}}]}
}
  • 字段说明:
    took : 耗时毫秒数,
    hits : total :总命中数量,
    hits : _index 索引,_type 文档,_source 显示字段列表,_class 对应实体类路径
Java Template使用(基础使用)
查询
在编写Java中查询语句时,可以使用SearchQueryBuilder 进行封装实际需要的查询语句,一般情况下我使用NativeSearchQueryBuilder 较多,可以自定义的构建QueryBuilders中的各个查询,比如 boolQuery 、matchQuery、 matchPhraseQuery、rangeQuery等,一 一介绍如下,建议es原生的写法和Java中写法结合起来对照使用。
  • boolQuery
    booQuery 在ES原生语法中是bool ,作为一种是否判断,主要有三种判断 must 、must_not 、should ,等同理解为 and 、not 、or,在实际的查询中我们需要动态组合使用,首先了解下同一个查询在原生语法中应该怎么实现,代码如下:
# 查询税号为 91310230674560485R ,分机号为 1 ,并且发票作废状态不等于 1 的发票数据
GET fatp_sale_invoice/_search
{"query": {"bool": {"must": [{"match_phrase": {"companyId": "91310230674560485R"}},{"match_phrase": {"machineNo": "1"}}],"must_not": [{"match_phrase": {"isCancel": "1"}}]}},"track_total_hits":true
}
  • 在Java中实现,代码如下:
NativeSearchQueryBuilder searchQueryBuilder = new
NativeSearchQueryBuilder();BoolQueryBuilder
boolQueryBuilder =        QueryBuilders.boolQuery().must(QueryBuilders.matchPhraseQuery("companyId","91310230674560485R"))
.must(QueryBuilders.matchPhraseQuery("machineNo","1"))                .mustNot(QueryBuilders.matchPhraseQuery("isCancel","1"));
searchQueryBuilder.withQuery(boolQueryBuilder);
SearchHits<Invoice> search =
elasticsearchRestTemplate.search(searchQueryBuilder.build(), Invoice.class);
// 总数long totalHits = search.getTotalHits();
System.out.println(totalHits);
// 发票数据List<Invoice> invoiceList = search.get().map(SearchHit::getContent).collect(Collectors.toList());
  • rangeQuery
    范围查询对于一般的字段类型来说是没有难度的,原生代码如下:
GET fatp_sale_invoice/_search
{"query": {"bool": {"must": [{"match_phrase": {"companyId": "91310230674560485R"}},{"match_phrase": {"machineNo": "1"}},{"range": {"totalAmount": {"gte": 300,"lte": 10000}}},{"range": {"invoiceDate": {"gte": "2020-01-01T00:00:00","lte":"2020-01-31T23:59:59"}}}],"must_not": [{"match_phrase": {"isCancel": "1"}}]}},"track_total_hits":true
}
  • 区别在于日期范围的查询,es中默认日期存储的格式为 timestamp 类型,在显示和查询上不太方便,一般在存储日期类型的字段会指定格式,使用 fortmat 参数指定日期格式为: yyyy-mm-dd HH:mm:ss ,如下代码是上述返回中部分字段,
            "invoiceDate" : "2020-06-19T02:27:00","invoiceStatus" : "1","invoiceRemark" : "","buyerName" : "上海爱信诺航天信息有限公司","buyerTaxNo" : "913101047989613362",
  • 注意到,日期格式会在中间有个“T”字母,并且这个字母是无法使用格式化语句进行格式化的,所以目前来说,我的处理方案是自己拼接查询日期格式,上述原生查询在代码中实现如下:
NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();BoolQueryBuilder
boolQueryBuilder =        QueryBuilders.boolQuery()
.must(QueryBuilders.matchPhraseQuery("companyId", "91310230674560485R"))                .must(QueryBuilders.matchPhraseQuery("machineNo","1"))
.must(QueryBuilders.rangeQuery("totalAmount").gte(300).lte(1000))
.must(QueryBuilders.rangeQuery("invoiceDate").gte("2020-01-01T00:00:00").lte("2020-01-31T23:59:59"))                .mustNot(QueryBuilders.matchPhraseQuery("isCancel","1"));
searchQueryBuilder.withQuery(boolQueryBuilder);
// 查询
SearchHits<Invoice> search =
elasticsearchRestTemplate.search(searchQueryBuilder.build(), Invoice.class);
  • matchQuery、matchPhraseQuery、matchPhrasePrefixQuery
    在上面的查询中,使用到了matchPhraseQuery ,matchQuery/matchPhraseQuery/matchPhrasePrefixQuery 这三个关键字查询的区别主要在于 matchQuery 是直接进行查询,在文本字段上进行全文检索,一般更更多是结合分词器进行使用。matchPhraseQuery 短语查询,查询字段中某一部分完全匹配查询条件。matchPhrasePrefixQuery 短语查询的基础下,对文本最后一个字段进行前缀匹配。
  • multiMatchQuery
    多字段匹配同一个值,比如以下dsl:
GET fatp_sale_invoice/_search
{"query": {"bool": {"must": [{"match_phrase": {"invoiceNum": "112233"}},{"match_phrase": {"invoiceCode": "122"}}]}},"track_total_hits":true
}
  • 查询发票代码、发票号码等于 112233 的结果,以上查询可以优化为:
GET fatp_sale_invoice/_search
{"query": {"multi_match": {"query": "112233","fields": ["invoiceCode","invoiceNum"]}}
}
  • 并且可以匹配正则模糊匹配,比如查询所有字段前缀为 invoice* 的字段,都等于 112233 的值,如下:

GET fatp_sale_invoice/_search
{"query": {"multi_match": {"query": "112233","fields": ["invoice*"]}}
}
  • java中实现代码如下:
NativeSearchQueryBuilder searchQueryBuilder = new
NativeSearchQueryBuilder();MultiMatchQueryBuilder multiMatchQueryBuilder =
QueryBuilders.multiMatchQuery("11223", new String[]{"invoiceCode", "invoiceNum"});
searchQueryBuilder.withQuery(multiMatchQueryBuilder);SearchHits<Invoice> search =
elasticsearchRestTemplate.search(searchQueryBuilder.build(), Invoice.class);
  • termsQuery
    单个字段匹配多个值,类似于 in,可以通过一个字段,判断是否同时匹配多个值列表,原生语法如下:
GET fatp_sale_invoice/_search
{"query": {"terms": {"invoiceCode": ["11","22"]}}
}
  • 代码查询为,查询发票代码在[“11”,“22”]的数据,在java中实现如下:
NativeSearchQueryBuilder searchQueryBuilder = new
NativeSearchQueryBuilder();TermsQueryBuilder termsQuery = QueryBuilders.termsQuery("invoiceCode",
new String[]{"11", "22"});
searchQueryBuilder.withQuery(termsQuery);
SearchHits<Invoice> search =
elasticsearchRestTemplate.search(searchQueryBuilder.build(), Invoice.class);
  • filter
    上文提到过,es 中查询过滤是可以实现类似的效果的,两者区别就在于filter是不计算匹配得分的,只是简单决定文档是否匹配,主要用于过滤结构化的数据,并且可以缓存数据提高性能效率。举个使用栗子:
GET fatp_sale_invoice/_search
{"query": {"bool": {"must": [{"match_phrase": {"companyId": "91310230674560485R"}},{"match_phrase": {"invoiceStatus": "1"}}],"must_not": [{"match_phrase": {"isCancel": "1"}}], "filter": [{"range": {"invoiceDate": {"gte": "2020-01-01T00:00:00","lte": "2020-01-31T23:59:52"}}}]}} ,"sort": [{"invoiceNum": {"order": "desc"}}], "size": 1, "track_total_hits":true
}
  • filter 可以用在查询中直接使用,也可以作为聚合分组的条件,具体后续介绍,上述代码在java 中实现如下:
NativeSearchQueryBuilder searchQueryBuilder = new
NativeSearchQueryBuilder();BoolQueryBuilder
boolQueryBuilder =        QueryBuilders.boolQuery()
.must(QueryBuilders.matchPhraseQuery("companyId", "91310230674560485R"))                .must(QueryBuilders.matchPhraseQuery("invoiceStatus","1"))                .mustNot(QueryBuilders.matchPhraseQuery("isCancel","1"))
.filter(QueryBuilders.rangeQuery("invoiceDate").gte("2019-12-01T00:00:00").lte("2019-12-31T23:59:59"))                ;
FieldSortBuilder invoiceNumSort =
SortBuilders.fieldSort("invoiceNum").order(SortOrder.DESC);
searchQueryBuilder.withQuery(boolQueryBuilder).withSort(invoiceNumSort);
  • from size
    分页,es中的分页其实原则上来说就等价于sql 中的limit,区别在于,limit可以接受一个参数,但在es中,如果仅仅需要查询数量,可以直接使用size后面接数量,如果是一个范围的,就需要 from 和 size 一起用,具体代码如下:
GET fatp_sale_invoice/_search
{"query": {"match_all": {}},"from": 0, "size": 20,"track_total_hits": true
}
  • 这段代码时最简单的分页查询,java 中实现也是比较简单,具体关键代码如下:
searchQueryBuilder.withPageable(PageRequest.of(0,20));
  • sort
    排序,这里说的排序仅仅是查询的排序,并不包含聚合后排序以及过滤后的排序,基础的排序语法,原生代码如下:
GET fatp_sale_invoice/_search
{"query": {"match_all": {}},"from": 0, "size": 20,"sort": [{"invoiceNum": {"order": "desc"}}], "track_total_hits": true
}

这段代码时分页后,根据发票号码升序排序,Java中代码实现如下:

FieldSortBuilder invoiceNumSort =
SortBuilders.fieldSort("invoiceNum").order(SortOrder.DESC);searchQueryBuilder.withSort(invoiceNumSort);
  • aggregation
    聚合应该算是es中最核心也最重要的部分,这里仅仅会简单介绍下聚合的使用,详细点的介绍会在后续中进行介绍,参考的文章如下 Elasticsearch 聚合的重要概念,下面介绍一个简单使用,根据票种进行聚合,展示的字段为发票代码、号码,然后计算每个票种类型下发票金额合计,原生代码如下:
GET fatp_sale_invoice/_search
{"query": {"bool": {"must": [{"match_phrase": {"companyId": "91310230674560485R"}},{"match_phrase": {"invoiceStatus": "1"}}],"must_not": [{"match_phrase": {"isCancel": "1"}}], "filter": [{"range": {"invoiceDate": {"gte": "2020-01-01T00:00:00","lte": "2020-01-31T23:59:52"}}}]}} ,"aggs": {"invTypeAggs": {"terms": {"field": "invType"},"aggs": {"mytopHits":{"top_hits": {"size": 1,"_source": ["invoiceCode","invoiceNum"]}},"sumTotalMoney": {"sum": {"field": "totalAmount"}}} }}, "size": 0, "track_total_hits":true
}
  • 返回结果:
{"took" : 10,"timed_out" : false,"_shards" : {"total" : 6,"successful" : 6,"skipped" : 0,"failed" : 0},"hits" : {"total" : {"value" : 170,"relation" : "eq"},"max_score" : null,"hits" : [ ]},"aggregations" : {"invTypeAggs" : {"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 0,"buckets" : [{"key" : "s","doc_count" : 97,"mytopHits" : {"hits" : {"total" : {"value" : 97,"relation" : "eq"},"max_score" : 5.207824E-4,"hits" : [{"_index" : "fatp_sale_invoice","_type" : "_doc","_id" : "10444391","_score" : 5.207824E-4,"_source" : {"invoiceCode" : "3100192130","invoiceNum" : "51934230"}}]}},"sumTotalMoney" : {"value" : 1120339.42}},{"key" : "p","doc_count" : 56,"mytopHits" : {"hits" : {"total" : {"value" : 56,"relation" : "eq"},"max_score" : 5.207824E-4,"hits" : [{"_index" : "fatp_sale_invoice","_type" : "_doc","_id" : "10444738","_score" : 5.207824E-4,"_source" : {"invoiceCode" : "031001900111","invoiceNum" : "89872110"}}]}},"sumTotalMoney" : {"value" : 7200.0}},{"key" : "c","doc_count" : 17,"mytopHits" : {"hits" : {"total" : {"value" : 17,"relation" : "eq"},"max_score" : 5.207824E-4,"hits" : [{"_index" : "fatp_sale_invoice","_type" : "_doc","_id" : "10444693","_score" : 5.207824E-4,"_source" : {"invoiceCode" : "031001800304","invoiceNum" : "44351980"}}]}},"sumTotalMoney" : {"value" : 30180.0}}]}}
}
  • 这种聚合的查询主要结果看 aggregations节点下的数据,简单解释下字段含义:
    buckets : 桶,将发票票种作为桶,可以分为 p,c,s三个桶,
    buckets - mytopHits - hits: 具体展示的数据
    buckets - sumTotalMoney : 指标,某一个桶下面的合计含税金额

  • 在java 中实现如下:

NativeSearchQueryBuilder searchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQueryBuilder =       QueryBuilders.boolQuery()                .must(QueryBuilders.matchPhraseQuery("companyId", "91310230674560485R"))                .must(QueryBuilders.matchPhraseQuery("invoiceStatus","1"))                .mustNot(QueryBuilders.matchPhraseQuery("isCancel","1"))
.filter(QueryBuilders.rangeQuery("invoiceDate").gte("2019-12-01T00:00:00").lte("2019-12-31T23:59:59")) ;
TermsAggregationBuilder termsAggregationBuilder =
AggregationBuilders.terms("invTypeAggs").field("invType")
.subAggregation(AggregationBuilders.topHits("mytopHits").sort("invoiceNum", SortOrder.ASC)
.fetchSource(new String[]{"invoiceCode","invoiceNum"},null).size(1))
.subAggregation(AggregationBuilders.sum("totalAmount").field("invoiceDetails.detailTotalAmount"));
searchQueryBuilder.withQuery(boolQueryBuilder).addAggregation(termsAggregationBuilder);
  • 对代码层次进行简单介绍下,首先 booleanQueryBuilder 是一个查询以及过滤的构建条件,
    termsAggregationBuilder 是为了根据 invType 分组,起名:invTypeAggs,在此基础上进行过滤除需要展示的发票代码、号码,显示数量为1,同层级下对每一个分组进行金额汇总统计,这种写法不难,比较关键点在于如果获取其中的值,首先获取我们需要展示的invoiceCode,invoiceNum ,代码如下:
SearchHits<InvoiceOrg> search =
elasticsearchRestTemplate.search(searchQueryBuilder.build(),
InvoiceOrg.class);
Map<String, Aggregation> stringAggregationMap = search.getAggregations().asMap();
// 获取需要展示的数据
Terms term = (Terms) stringAggregationMap.get("invTypeAggs");
List<? extends Terms.Bucket> buckets = term.getBuckets();
for(Terms.Bucket bucket : buckets){    String keyAsString = bucket.getKeyAsString();    System.out.println("keyAsString:"+keyAsString);    // 获取每个桶下的含税金额Sum totalAmountSum = (Sum) bucket.getAggregations().asMap().get("totalAmount");TopHits topHits = bucket.getAggregations().get("mytopHits");    for(SearchHit hit : topHits.getHits()){        System.out.println("id ->"+hit.getId()+hit.getSourceAsMap().toString());    }
}
  • 以上是SpringBoot整合ES的简单操作,比较粗糙,希望不要介意,后续会增加更多细节以及用法,会针对比如 boolQuery 里面的must 、must_not、should 顺序以及嵌套层级分别会怎么影响结果以及评分进行单独介绍,聚合分组更是重中之重,也会在后续文章中一一介绍。
结束;

SpringBoot整合Elasticsearch(一)相关推荐

  1. es springboot 不设置id_原创 | 一篇解决Springboot 整合 Elasticsearch

    ElasticSearch 结合业务的场景,在目前的商品体系需要构建搜索服务,主要是为了提供用户更丰富的检索场景以及高速,实时及性能稳定的搜索服务. ElasticSearch是一个基于Lucene的 ...

  2. 七、SpringBoot整合elasticsearch集群

    @Author : By Runsen @Date : 2020/6/12 作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件.导致翘 ...

  3. SpringBoot整合ElasticSearch实现多版本的兼容

    前言 在上一篇学习SpringBoot中,整合了Mybatis.Druid和PageHelper并实现了多数据源的操作.本篇主要是介绍和使用目前最火的搜索引擎ElastiSearch,并和Spring ...

  4. SpringBoot整合elasticsearch (java整合es)

    欢迎大家进群,一起探讨学习 微信公众号,每天给大家提供技术干货 博主技术笔记 博主网站地址1 博主网站地址2 博主开源微服架构前后端分离技术博客项目源码地址,欢迎各位star SpringBoot整合 ...

  5. SpringBoot 整合ElasticSearch全文检索

    ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口.Elasticsearch是用Java语言开发的,并作为Apa ...

  6. Springboot整合Elasticsearch(High-level-Client)

    前言 通过学习Elasticsearch一小段时间来稍微认识了一点ES的体系架构.发现ES最大的坑就是版本兼容性问题了-在整合Springboot也不例外,但是,有一种方式能较好的解决-通过restc ...

  7. Elasticsearch的安装,以及Springboot整合Elasticsearch

    *一.下载好elasticsearch并解压 我这里用的是elasticsearch-5.6.8,下面是下载地址 https://artifacts.elastic.co/downloads/elas ...

  8. SpringBoot整合Elasticsearch详细步骤以及代码示例(附源码)

    准备工作# 环境准备# JAVA版本 Copy java version "1.8.0_121" Java(TM) SE Runtime Environment (build 1. ...

  9. 【十九】springboot整合ElasticSearch实战(万字篇)

    本章开始学习springboot整合ElasticSearch 7.X版本并通过小demo实现基本的增删改查.实现如下案例: 1.当向数据新增一个商品信息时,同时向rabbitMQ发起消息(异步实现) ...

  10. Springboot 整合ElasticSearch 常用的插入查询,模糊查询,范围查询

    前言 本来该篇教程就应该写到 Springboot 整合 ElasticSearch 入门教学必看 https://blog.csdn.net/qq_35387940/article/details/ ...

最新文章

  1. windows端口查看及进程查找
  2. Angular jasmine单元测试框架spied method的调用记录数据结构
  3. hadoop安装hive及配置mysql_Hadoop系列之Hive(数据仓库)安装配置
  4. P3193 [HNOI2008]GT考试
  5. linux /etc/passwd
  6. Java web 基础
  7. maven安装junit_JUnit安装Maven – JUnit 4和JUnit 5
  8. 数组 / 伪数组 判断及方法调用 (权威指南笔记)
  9. 详细解析堆排序java实现
  10. SPSS25安装教程
  11. openwrt 问题四 9531编译解决方法
  12. 【转】2018秋招面经
  13. MEM/MBA英语基础(10)非谓语动词
  14. java hex to ascii_在java中读取hex文件并将其转换为ascii
  15. teamview删除设备
  16. 2022-04-24_数组的定义和初始化
  17. adobe flash(转载)
  18. sklearn常用工具
  19. html立体魔方图片制作,各种三D立体魔方相册制作代码
  20. Mysql删除分区,增加分区,分区数据清理

热门文章

  1. 字符串删除重复字符_高效的字符串清理-删除内部重复空间
  2. 微信公众号文章转pdf下载,不难也不太容易,磕磕绊绊倒是不少如何用xpath保存网站源码;如何精简你的文章请求链接;如何将文章转化为pdf文件,不乱码,不报错
  3. RTL8192CUS驱动程序编译
  4. arcgis计算几何-已禁用
  5. 某视频(dy)创作者平台上传视频步骤分析及authorization,CRC32参数
  6. H264VideoToolBox硬件解码
  7. 从初级软件测试,到高级软件测试的必经之路
  8. 软件项目管理第4版课后习题[附解析]第十四章
  9. 1169:大整数减法
  10. 2018 Multi-University Training Contest 2