赞
踩
一、Spring Data
1、简介
Spring Data 是一个用于简化数据库、非关系型数据库、索引库访问,并支持云服务的开源框架。Spring Data 可以极大的简化JPA的写法,可以在几乎不用写实现的情况下,实现对数据库的访问和操作。除了 CRUD 之外,还包括如分页、排序等一下常用的功能。
2、Spring Data 的官网
https://spring.io/projects/spring-data
二、java 集成 Spring Data Elasticsearch 操作 es
1、pom依赖(spring boot 版本 2.4.1)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
备注:
spring-data-elasticsearch 版本 4.1.1
elasticsearch 版本 7.11.1
elasticsearch-rest-high-level-client 版本 7.11.1
2、application.yml 配置文件
server: port: 8015 spring: application: name: test-15-elasticsearch elasticsearch: rest: # es 连接地址,多个用逗号隔开 uris: 192.168.3.56:9200,192.168.3.57:9200,192.168.3.58:9200 username: spadger password: spadger # 连接超时时间,单位毫秒,默认1s connection-timeout: 1000 # 读取超时时间,单位毫秒,默认30s read-timeout: 1000
3、使用 ElasticsearchRestTemplate 客户端操作 es 索引库
1)Product 实体类代码
package com.test.elasticsearch.entity; import com.fasterxml.jackson.annotation.JsonFormat; import java.util.Date; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Document; import java.io.Serializable; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; /** * indexName 指定索引名 * shards 指定分片(无特殊配置,默认即可) * replicas 指定副本集(无特殊配置,默认即可) */ @Data @Document(indexName = "product", shards = 1, replicas = 1) public class Product implements Serializable { private static final long serialVersionUID = 551589397625941751L; @Id private Long id;//商品唯一标识 @Field(type = FieldType.Text) private String name;//商品名称 @Field(type = FieldType.Keyword) private String category;//商品分类 @Field(type = FieldType.Double) private Double price;//商品价格 @Field(type = FieldType.Date, format = DateFormat.custom,pattern = "yyyy-MM-dd") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd",timezone="GMT+8") private Date upTime;//商品上架时间 @Field(type = FieldType.Date, format = DateFormat.date_optional_time) @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZ",timezone="GMT+8") private Date downTime;//商品下架时间 @Field(type = FieldType.Keyword, index = false)//index = false 表示该字段不能作为查询条件 private String images;//图片地址 }
2)SpringDataESIndexTest 测试类
package com.test.elasticsearch; import com.test.elasticsearch.entity.Product; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.MatchPhraseQueryBuilder; import org.elasticsearch.index.query.MatchQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryStringQueryBuilder; import org.elasticsearch.index.query.RangeQueryBuilder; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.IndexOperations; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.NativeSearchQuery; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.Query; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.core.query.UpdateResponse; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * 使用 Spring Data Elasticsearch 操作 es 索引库 * ElasticsearchRestTemplate 客户端测试 */ @Slf4j @RunWith(SpringJUnit4ClassRunner.class)//不加这个注解 service层 就无法注入 @SpringBootTest public class SpringDataESIndexTest { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; /** * 创建索引 */ @Test public void createIndex() { /** * 方式一:指定索引库名称创建索引(只会创建一个名为 product 的索引库,不会创建映射) * 存在问题:创建索引之后,删除索引,再重新创建的时候,会报错 "reason":"index [product/906Vi3zzRaKb1zJaIKzvtg] already exists" * 报错原因:分词器反复创建:先用standard分词器建的index,然后使用ik分词器又建索引出现这个错误,相当于对已有的 index 修改 mapping * 报错解决办法:不创建索引就好了。如果已经删除掉了 product 索引库,es里是没有 product 索引的。但实体类 Product 里有"indexName = “product”",你往索引里插入数据时,索引就自动存在了 */ IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(IndexCoordinates.of("product")); boolean result = indexOperations.create(); System.out.println("创建索引结果:" + result); boolean putMappingResult = indexOperations.putMapping(Product.class); System.out.println("索引映射添加结果:" + putMappingResult); /** * 方式二:指定实体类创建索引(创建一个名为 product 的索引库,同时会自动创建映射,映射关系就对应实体类 Product.class 的属性) * 存在问题:创建索引之后,删除索引,再重新创建的时候,会报错 "reason":"index [product/906Vi3zzRaKb1zJaIKzvtg] already exists" * 报错原因:分词器反复创建:先用standard分词器建的index,然后使用ik分词器又建索引出现这个错误,相当于对已有的 index 修改 mapping * 报错解决办法:不创建索引就好了。如果已经删除掉了 product 索引库,es里是没有 product 索引的。但实体类 Product 里有"indexName = “product”",你往索引里插入数据时,索引就自动存在了 */ // IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Product.class); // boolean result = indexOperations.create(); // System.out.println("创建索引结果:" + result); } /** * 根据索引名称判断索引是否存在 */ @Test public void existsIndex() { //方式一:单纯的根据索引库名称进行判断 boolean result01 = elasticsearchRestTemplate.indexOps(IndexCoordinates.of("product")).exists(); System.out.println("索引是否存在:" + result01); //方式二:根据索引库对应的实体类,也就是加了 @Document(indexName = "product", shards = 1, replicas = 1) 注解的实体类进行判断 boolean result02 = elasticsearchRestTemplate.indexOps(Product.class).exists(); System.out.println("索引是否存在:" + result02); } /** * 根据索引名称删除索引库 */ @Test public void deleteIndex() { boolean result = false; IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Product.class); System.out.println("索引是否存在:" + indexOperations.exists()); if (indexOperations.exists()) { result = indexOperations.delete(); } System.out.println("删除结果:" + result); System.out.println("索引是否存在:" + indexOperations.exists()); } /** * 创建文档(索引库插入数据) * 注意: * ① 假如索引库 product 不存在,在添加文档的时候会自动创建 product 索引库,索引库信息会和 Product.class 中的各个属性一一对应 * ② 创建文档的时候如果指定了id,则会使用指定的id,如果没有指定文档id,es默认会自动创建一个uuid作为文档id * ③ 创建文档的时候如果指定了id,并且该文档已经存在,则默认为更新操作 */ @Test public void createDocument() { Product product = new Product(); product.setId(1001L); product.setTitle("小米 S12"); product.setImages("F:\\pictures\\yy测试下载图片.jpg"); product.setPrice(3688.99); product.setCategory("手机"); Product saveResult = elasticsearchRestTemplate.save(product); System.out.println("文档新增,指定id,创建结果:" + saveResult); Product product01 = new Product(); product01.setTitle("一加 T3"); product01.setImages("F:\\pictures\\yy测试下载图片.jpg"); product01.setPrice(4666.88); product01.setCategory("手机"); Product saveResult01 = elasticsearchRestTemplate.save(product01); System.out.println("文档新增,不指定id,创建结果:" + saveResult01); } /** * 批量创建文档(索引库批量插入数据) * 注意: * ① 假如索引库 product 不存在,在添加文档的时候会自动创建 product 索引库,索引库信息会和 Product.class 中的各个属性一一对应 * ② 创建文档的时候如果指定了id,则会使用指定的id,如果没有指定文档id,es默认会自动创建一个uuid作为文档id * ③ 创建文档的时候如果指定了id,并且该文档已经存在,则默认为更新操作 */ @Test public void createDocumentList() { List<Product> list = new ArrayList<>(); Product product01 = new Product(); product01.setId(1001L); product01.setTitle("小米 S12"); product01.setImages("F:\\pictures\\yy测试下载图片.jpg"); product01.setPrice(3688.99); product01.setCategory("手机"); list.add(product01); Product product02 = new Product(); product02.setId(1003L); product02.setTitle("一加 T3"); product02.setImages("F:\\pictures\\yy测试下载图片.jpg"); product02.setPrice(4666.88); product02.setCategory("手机"); Product product03 = new Product(); list.add(product02); product03.setId(1002L); product03.setTitle("S11"); product03.setImages("F:\\pictures\\yy测试下载图片.jpg"); product03.setPrice(2999.99); product03.setCategory("小米手机"); list.add(product03); Iterable<Product> saveResult = elasticsearchRestTemplate.save(list); System.out.println("文档批量新增,创建结果:" + saveResult); } /** * 根据文档id判断文档是否存在 */ @Test public void existsDocument() { boolean result = elasticsearchRestTemplate.exists("1001", Product.class); System.out.println("文档是否存在:" + result); } /** * 根据文档id删除文档 */ @Test public void deleteDocument() { String result = elasticsearchRestTemplate.delete("j07wvIQBG2vb63j9iSAY", Product.class); System.out.println("根据文档id删除,结果::" + result); } /** * 根据查询条件批量删除文档 */ @Test public void deleteDocumentByQueryCondition() { QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery("小米"); NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryStringQueryBuilder); elasticsearchRestTemplate.delete(nativeSearchQuery, Product.class); System.out.println("根据查询条件批量删除文档!!!"); } /** * 批量修改文档 */ @Test public void updateDocumentList() { List<Product> list = new ArrayList<>(); Product product01 = new Product(); product01.setId(1003L); product01.setTitle("一加 T3"); product01.setImages("F:\\pictures\\yy测试下载图片.jpg"); product01.setPrice(4688.88); product01.setCategory("手机"); list.add(product01); Product product03 = new Product(); product03.setId(1004L); product03.setTitle("一加 T3 PRO"); product03.setImages("F:\\pictures\\yy测试下载图片.jpg"); product03.setPrice(5499.99); product03.setCategory("手机"); list.add(product03); Iterable<Product> saveResult = elasticsearchRestTemplate.save(list); System.out.println("批量修改文档,结果:" + saveResult); } /** * 局部修改文档 */ @Test public void updateDocument() { //ctx._source 为固定内容,title、price 为文档属性名称(字段名) String script = "ctx._source.title='小米 S12 PRO';ctx._source.price=3999.99"; //根据文档id修改文档指定内容 UpdateQuery updateQuery = UpdateQuery.builder("1001").withScript(script).build(); IndexCoordinates indexCoordinates = IndexCoordinates.of("product"); UpdateResponse updateResponse = elasticsearchRestTemplate.update(updateQuery, indexCoordinates); System.out.println("局部修改文档,结果:" + updateResponse); } /** * 查询文档 -- 查询全部 */ @Test public void searchDocumentMatchAll() { MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(matchAllQueryBuilder); SearchHits<Product> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Product.class); List<Product> list = new ArrayList<>(); System.out.println("查询文档,查询全部,查询总数:" + searchHits.getTotalHits()); searchHits.forEach(sh -> { list.add(sh.getContent()); }); System.out.println("查询文档,查询全部,结果集:" + list); } /** * 查询文档 -- 根据文档id查询 */ @Test public void searchDocumentById() { Product product = elasticsearchRestTemplate.get("1001", Product.class); System.out.println("查询文档,根据文档id查询,结果集:" + product); } /** * 查询文档 -- 模糊查询 */ @Test public void searchDocumentLike() { //QueryBuilders.queryStringQuery("小米") 这种查询方式不指定匹配的字段,默认根据文档中除id外的第一个字段进行匹配,并且只会通过这一个字段进行匹配,而且查询条件会被分词,根据分词后的结果进行匹配 QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery("小米"); NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryStringQueryBuilder); SearchHits<Product> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Product.class); List<Product> list = new ArrayList<>(); System.out.println("查询文档,模糊查询,查询总数:" + searchHits.getTotalHits()); searchHits.forEach(sh -> { list.add(sh.getContent()); }); System.out.println("查询文档,模糊查询,结果集:" + list); } /** * 查询文档 -- 使用 match 查询 -- 模糊查询 */ @Test public void searchDocumentMatch() { //QueryBuilders.matchQuery("title", "小米") 指定具体的匹配字段,查询条件会被分词,根据分词后的结果进行匹配 MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("title", "小米"); NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(matchQueryBuilder); SearchHits<Product> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Product.class); List<Product> list = new ArrayList<>(); System.out.println("查询文档,使用 match 查询,模糊查询,查询总数:" + searchHits.getTotalHits()); searchHits.forEach(sh -> { list.add(sh.getContent()); }); System.out.println("查询文档,使用 match 查询,模糊查询,结果集:" + list); } /** * 查询文档 -- 使用 match 查询 -- 模糊查询之短语搜索 * 备注:短语搜索是对条件不分词,但是文档中属性根据配置实体类时指定的分词类型进行分词,如果属性使用ik分词器,从分词后的索引数据中进行匹配。 */ @Test public void searchDocumentMatchPhrase() { //QueryBuilders.matchPhraseQuery("title", "小米") 指定具体的匹配字段为 title,查询条件不分词,直接匹配 MatchPhraseQueryBuilder matchPhraseQueryBuilder = QueryBuilders.matchPhraseQuery("title", "小米"); NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(matchPhraseQueryBuilder); SearchHits<Product> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Product.class); List<Product> list = new ArrayList<>(); System.out.println("查询文档,使用 match 查询,模糊查询,查询总数:" + searchHits.getTotalHits()); searchHits.forEach(sh -> { list.add(sh.getContent()); }); System.out.println("查询文档,使用 match 查询,模糊查询,结果集:" + list); } /** * 查询文档 -- 区间查询 * 备注:短语搜索是对条件不分词,但是文档中属性根据配置实体类时指定的分词类型进行分词,如果属性使用ik分词器,从分词后的索引数据中进行匹配。 */ @Test public void searchDocumentRange() { //QueryBuilders.rangeQuery("price") 根据 price 字段进行区间查询。gte():大于等于,lte():小于等于,gt():大于,lt():小于。 RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price").gte(4688.88D).lte(6000D);//gte(4688.88D) 和 gte(4688.88) 效果相同 NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(rangeQueryBuilder); SearchHits<Product> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Product.class); List<Product> list = new ArrayList<>(); System.out.println("查询文档,区间查询,查询总数:" + searchHits.getTotalHits()); searchHits.forEach(sh -> { list.add(sh.getContent()); }); System.out.println("查询文档,区间查询,结果集:" + list); } /** * 查询文档 -- 简单多条件查询 * 实例:查询商品名称中包含 “米” 字,且价格大于等于 2555.88 的所有商品 */ @Test public void searchDocumentMoreCondition() { //QueryBuilders.rangeQuery("price") 根据 price 字段进行区间查询。gte():大于等于,lte():小于等于,gt():大于,lt():小于。 List<QueryBuilder> queryBuilderList = new ArrayList<>(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); queryBuilderList.add(QueryBuilders.matchQuery("title","米"));//商品名称 中要包含 “米” 字 queryBuilderList.add(QueryBuilders.rangeQuery("price").gte(2555.88));//商品价格 要大于等于 2555.88 // boolQueryBuilder.must().addAll(queryBuilderList);//逻辑 与(and -- 多个条件都要满足) boolQueryBuilder.should().addAll(queryBuilderList);//逻辑 或(or -- 多个条件满足其一即可) NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(boolQueryBuilder); SearchHits<Product> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Product.class); List<Product> list = new ArrayList<>(); System.out.println("查询文档,简单多条件查询,查询总数:" + searchHits.getTotalHits()); searchHits.forEach(sh -> { list.add(sh.getContent()); }); System.out.println("查询文档,简单多条件查询,结果集:" + list); } /** * 查询文档 -- 复杂多条件查询 * 实例:查询商品名称包含 “小米” 或者 “一加”,并且商品类型为 “手机” 的所有商品 */ @Test public void searchDocumentMoreConditionOther() { //封装第一个查询条件 List<QueryBuilder> queryBuilderList01 = new ArrayList<>(); BoolQueryBuilder boolQueryBuilder01 = QueryBuilders.boolQuery(); queryBuilderList01.add(QueryBuilders.matchQuery("title","小米"));//商品名称 中要包含 “小米” 关键字 queryBuilderList01.add(QueryBuilders.matchQuery("category","手机"));//商品类型 要是 “手机” boolQueryBuilder01.must().addAll(queryBuilderList01);//逻辑 与(and -- 多个条件都要满足) //封装第二个查询条件 List<QueryBuilder> queryBuilderList02 = new ArrayList<>(); BoolQueryBuilder boolQueryBuilder02 = QueryBuilders.boolQuery(); queryBuilderList02.add(QueryBuilders.matchQuery("title","一加"));//商品名称 中要包含 “一加” 关键字 queryBuilderList02.add(QueryBuilders.matchQuery("category","手机"));//商品类型 要是 “手机” boolQueryBuilder02.must().addAll(queryBuilderList02);//逻辑 与(and -- 多个条件都要满足) //将 条件一 和 条件二 封装到 条件三 中,再使用 条件三 作为最后的查询条件 List<QueryBuilder> queryBuilderList03 = new ArrayList<>(); BoolQueryBuilder boolQueryBuilder03 = QueryBuilders.boolQuery(); queryBuilderList03.add(boolQueryBuilder01); queryBuilderList03.add(boolQueryBuilder02); boolQueryBuilder03.should().addAll(queryBuilderList03);//逻辑 或(or -- 多个条件满足其一即可) //使用 条件三 作为最后的查询条件进行查询 NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(boolQueryBuilder03); SearchHits<Product> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Product.class); List<Product> list = new ArrayList<>(); System.out.println("查询文档,复杂多条件查询,查询总数:" + searchHits.getTotalHits()); searchHits.forEach(sh -> { list.add(sh.getContent()); }); System.out.println("查询文档,复杂多条件查询,结果集:" + list); } /** * 查询文档 -- 分页与排序 * 实例:查询商品名称中包含 “米” 字,且价格大于等于 2555.88 的所有商品 */ @Test public void searchDocumentPageAndOrder() { MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); Query query = new NativeSearchQuery(matchAllQueryBuilder); //排序(Direction.ASC:升序;Direction.DESC:降序) query.addSort(Sort.by(Direction.ASC,"id")); //分页(起始页:0) query.setPageable(PageRequest.of(0,2)); SearchHits<Product> searchHits = elasticsearchRestTemplate.search(query, Product.class); List<Product> list = new ArrayList<>(); System.out.println("查询文档,分页与排序,查询总数:" + searchHits.getTotalHits() + ",当前页的数量:" + searchHits.getSearchHits().size()); searchHits.forEach(sh -> { list.add(sh.getContent()); }); System.out.println("查询文档,分页与排序,结果集:" + list); } /** * 查询文档 -- 去重 * 实例:查询商品名称中包含 “米” 字,且价格大于等于 2555.88 的所有商品 * 注意:去重的字段不能是 text 类型 */ @Test public void searchDocumentCollapse() { MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(matchAllQueryBuilder); nativeSearchQueryBuilder.withQuery(boolQueryBuilder); //根据商品类型 category 进行去重,去重的字段一定不能是 text 类型 nativeSearchQueryBuilder.withCollapseField("category"); SearchHits<Product> searchHits = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), Product.class); List<Product> list = new ArrayList<>(); System.out.println("查询文档,去重,查询总数:" + searchHits.getTotalHits() + ",去重后的数量:" + searchHits.getSearchHits().size()); searchHits.forEach(sh -> { list.add(sh.getContent()); }); System.out.println("查询文档,去重,结果集:" + list); } /** * 查询文档 -- 分组聚合 * 实例:根据商品类型进行分类,查询所有类型对应的商品数量 * 注意:作为聚合的字段不能是 text 类型 */ @Test public void searchDocumentAggregations() { MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.must(matchAllQueryBuilder); nativeSearchQueryBuilder.withQuery(boolQueryBuilder); //AggregationBuilders.terms("product_count") 中 product_count 是分组后的结果集对应的 key,field("category") 中的 category 为分组的字段 nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("product_count").field("category")); SearchHits<Product> searchHits = elasticsearchRestTemplate.search(nativeSearchQueryBuilder.build(), Product.class); Aggregations aggregations = searchHits.getAggregations();//分组聚合后的结果集对象 ParsedStringTerms product_count = aggregations.get("product_count"); Map<String,Long> map = new HashMap<>(); System.out.println("查询文档,分组聚合,分组聚合后的结果集对象:" + product_count); for (Terms.Bucket bucket: product_count.getBuckets()) { map.put(bucket.getKeyAsString(),bucket.getDocCount()); } System.out.println("查询文档,分组聚合,结果集:" + map); } }
4、使用 RestHighLevelClient 客户端操作 es 索引库
1)User 实体类
package com.test.elasticsearch.entity; import java.util.Date; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; @Data @NoArgsConstructor @AllArgsConstructor @ToString @Document(indexName = "user", shards = 1, replicas = 1) public class User { @Id @Field(type = FieldType.Long) private Long id; @Field(type = FieldType.Text) private String name; @Field(type = FieldType.Keyword) private String sex; @Field(type = FieldType.Integer) private Integer age; @Field(type = FieldType.Keyword) private String role; @Field(type = FieldType.Date) private Date birthday; @Field(type = FieldType.Long) private Long worth; @Field(type = FieldType.Boolean) private boolean isDied; }
2)RestHighLevelClient 客户端测试类
package com.test.elasticsearch; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.ObjectMapper; import com.test.elasticsearch.entity.User; import java.io.IOException; import java.util.Date; import lombok.extern.slf4j.Slf4j; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.indices.CreateIndexRequest; import org.elasticsearch.client.indices.CreateIndexResponse; import org.elasticsearch.client.indices.GetIndexRequest; import org.elasticsearch.client.indices.GetIndexResponse; import org.elasticsearch.common.unit.Fuzziness; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.FuzzyQueryBuilder; import org.elasticsearch.index.query.IdsQueryBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.RangeQueryBuilder; import org.elasticsearch.index.query.TermQueryBuilder; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.metrics.CardinalityAggregationBuilder; import org.elasticsearch.search.aggregations.metrics.ParsedAvg; import org.elasticsearch.search.aggregations.metrics.ParsedCardinality; import org.elasticsearch.search.aggregations.metrics.ParsedMax; import org.elasticsearch.search.aggregations.metrics.ParsedMin; import org.elasticsearch.search.aggregations.metrics.ParsedStats; import org.elasticsearch.search.aggregations.metrics.ParsedValueCount; import org.elasticsearch.search.aggregations.metrics.StatsAggregationBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.SortOrder; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * 使用 Spring Data Elasticsearch 操作 es 索引库 * RestHighLevelClient 客户端测试 */ @Slf4j @RunWith(SpringJUnit4ClassRunner.class)//不加这个注解 service层 就无法注入 @SpringBootTest public class SpringDataESIndexRestHighTest { @Autowired private RestHighLevelClient restHighLevelClient; /** * 创建索引 */ @Test public void restHighCreateIndex() throws IOException { //创建创建索引的请求对象 CreateIndexRequest createIndexRequest = new CreateIndexRequest("user"); //创建索引 CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT); //获取创建索引的响应状态 boolean acknowledged = createIndexResponse.isAcknowledged(); System.out.println("索引操作,创建索引,创建结果:" + acknowledged); } /** * 查询索引库的相关信息 */ @Test public void restHighQueryIndexsInfo() throws IOException { GetIndexRequest getIndexRequest = new GetIndexRequest("user"); GetIndexResponse getIndexResponse = restHighLevelClient.indices().get(getIndexRequest, RequestOptions.DEFAULT); //获取创建索引的响应状态 //获取索引库的相关信息 System.out.println("索引库的别名:" + getIndexResponse.getAliases()); System.out.println("索引库的映射信息:" + JSON.toJSONString(getIndexResponse.getMappings())); System.out.println("索引库的配置信息:" + getIndexResponse.getSettings()); System.out.println("索引库的默认配置信息:" + getIndexResponse.getDefaultSettings()); System.out.println("不知道啥信息111:" + getIndexResponse.getIndices()); System.out.println("不知道啥信息222:" + getIndexResponse.getDataStreams()); } /** * 删除索引库 */ @Test public void restHighDeleteIndex() throws IOException { GetIndexRequest getIndexRequest = new GetIndexRequest("user"); System.out.println("索引操作,删除之前--判断索引是否存在,结果:" + restHighLevelClient.indices().exists(getIndexRequest,RequestOptions.DEFAULT)); DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("user"); AcknowledgedResponse acknowledgedResponse = restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT); System.out.println("索引操作,删除索引,删除结果:" + acknowledgedResponse.isAcknowledged()); System.out.println("索引操作,删除之后--判断索引是否存在,结果:" + restHighLevelClient.indices().exists(getIndexRequest,RequestOptions.DEFAULT)); } /** * 索引库添加文档 * 注意: * ① 如果索引库不存在,添加文档的时候,默认会自动创建索引库,前提是实体类中使用 @Document(indexName = "user", shards = 1, replicas = 1) 注解绑定了索引库 * ② 如果映射关系没有创建,添加文档的时候,默认会根据实体类中 @Field(type = FieldType.Text) 注解绑定的属性自动创建映射关系 */ @Test public void restHighAddDocument() throws IOException { IndexRequest indexRequest = new IndexRequest(); indexRequest.index("user").id("1001"); User user01 = new User(); user01.setId(1001L); user01.setName("萧炎"); user01.setSex("男"); user01.setAge(18); user01.setRole("男主"); //向es添加数据,必须将数据转换为json格式 ObjectMapper objectMapper = new ObjectMapper(); String user01Str = objectMapper.writeValueAsString(user01); indexRequest.source(user01Str, XContentType.JSON); //向索引库中插入数据 IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT); System.out.println("文档操作,添加文档,结果:" + indexResponse.getResult()); } /** * 索引库查询文档 */ @Test public void restHighGetDocument() throws IOException { GetRequest getRequest = new GetRequest(); getRequest.index("user").id("1001");//根据文档id查询 GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT); System.out.println("文档操作,查询文档,结果:" + getResponse.getSourceAsString()); } /** * 索引库修改文档 */ @Test public void restHighUpdateDocument() throws IOException { UpdateRequest updateRequest = new UpdateRequest(); updateRequest.index("user").id("1001"); updateRequest.doc(XContentType.JSON,"id",1002L); UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT); System.out.println("文档操作,修改文档,结果:" + updateResponse.getResult()); } /** * 索引库删除文档 */ @Test public void restHighDeleteDocument() throws IOException { GetRequest getRequest = new GetRequest(); getRequest.index("user").id("1001");//根据文档id查询 System.out.println("文档操作,删除之前 -- 判断文档是否存在,结果:" + restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT)); DeleteRequest deleteRequest = new DeleteRequest(); deleteRequest.index("user").id("1001");//根据文档id进行删除 DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT); System.out.println("文档操作,删除文档,结果:" + deleteResponse.getResult()); System.out.println("文档操作,删除之后 -- 判断文档是否存在,结果:" + restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT)); } /** * 索引库批量添加文档 */ @Test public void restHighAddDocumentBatch() throws IOException { BulkRequest bulkRequest = new BulkRequest(); bulkRequest.add(new IndexRequest().index("user").id("1001").source(XContentType.JSON,"name","萧炎","sex","男","age",18,"role","男主","birthday",new Date(1417251624000L),"worth",100000000L,"isDied",false)); bulkRequest.add(new IndexRequest().index("user").id("1002").source(XContentType.JSON,"name","云韵","sex","女","age",25,"role","女二","birthday",new Date(867574824000L),"worth",55555555L,"isDied",false)); bulkRequest.add(new IndexRequest().index("user").id("1003").source(XContentType.JSON,"name","萧熏儿","sex","女","age",18,"role","女一","birthday",new Date(1411981224000L),"worth",99999999L,"isDied",false)); bulkRequest.add(new IndexRequest().index("user").id("1004").source(XContentType.JSON,"name","美杜莎","sex","女","age",25,"role","女一","birthday",new Date(858934824000L),"worth",99999999L,"isDied",false)); bulkRequest.add(new IndexRequest().index("user").id("1005").source(XContentType.JSON,"name","药老","sex","男","age",55,"role","男二","birthday",new Date(-87145176000L),"worth",88888888L,"isDied",true)); bulkRequest.add(new IndexRequest().index("user").id("1006").source(XContentType.JSON,"name","海波东","sex","男","age",45,"role","男三","birthday",new Date(240915624000L),"worth",66666666L,"isDied",false)); bulkRequest.add(new IndexRequest().index("user").id("1007").source(XContentType.JSON,"name","石漠城萧鼎","sex","男","age",21,"role","男四","birthday",new Date(990349224000L),"worth",7895562L,"isDied",false)); bulkRequest.add(new IndexRequest().index("user").id("1008").source(XContentType.JSON,"name","石漠城萧厉","sex","男","age",20,"role","男四","birthday",new Date(1026982824000L),"worth",6975898L,"isDied",false)); bulkRequest.add(new IndexRequest().index("user").id("1009").source(XContentType.JSON,"name","萧战-萧族族主","sex","男","age",46,"role","男四","birthday",new Date(190198824000L),"worth",8546897L,"isDied",false)); BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT); System.out.println("文档操作,批量添加文档,耗时:" + bulkResponse.getTook()); } /** * 索引库批量删除文档 */ @Test public void restHighDeleteDocumentBatch() throws IOException { BulkRequest bulkRequest = new BulkRequest(); bulkRequest.add(new DeleteRequest().index("user").id("1001")); bulkRequest.add(new DeleteRequest().index("user").id("1002")); bulkRequest.add(new DeleteRequest().index("user").id("1003")); bulkRequest.add(new DeleteRequest().index("user").id("1004")); bulkRequest.add(new DeleteRequest().index("user").id("1005")); bulkRequest.add(new DeleteRequest().index("user").id("1006")); BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT); System.out.println("文档操作,批量删除文档,耗时:" + bulkResponse.getTook()); } /** * 高级查询之全量查询 */ @Test public void restHighSearchDocumentAll() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之全量查询,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之全量查询,查询耗时:" + searchResponse.getTook()); SearchHit[] result = hits.getHits(); for (SearchHit searchHit: result) { System.out.println("文档操作,高级查询之全量查询,id:" + searchHit.getId() + ",内容:" + searchHit.getSourceAsString()); } } /** * 高级查询之精确匹配 * 注意: * ① QueryBuilders.termQuery(String name,String value):termQuery是精确匹配,即输入的查询内容是什么,就会按照什么取查询,不会解析查询内容,进行分词。这里的 name 是文档中的字段名,value 是要查询的内容。 */ @Test public void restHighSearchDocumentTermQuery() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); //根据年龄字段查询 // searchRequest.source(new SearchSourceBuilder().query(QueryBuilders.termQuery("age",18))); //根据名称 name 字段精确匹配 searchRequest.source(new SearchSourceBuilder().query(QueryBuilders.termQuery("name","炎"))); //根据角色 role 字段精确匹配 // searchRequest.source(new SearchSourceBuilder().query(QueryBuilders.termQuery("role","男主"))); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之精确匹配,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之精确匹配,查询耗时:" + searchResponse.getTook()); hits.forEach(p -> { System.out.println("文档操作,高级查询之精确匹配,查询结果,文档id:" + p.getId() + ",文档原生信息:" + p.getSourceAsString()); }); } /** * 高级查询之布尔查询 */ @Test public void restHighSearchDocumentBoolQuery() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); boolQueryBuilder.should(QueryBuilders.rangeQuery("age").gte(20));//年龄大于等于20 boolQueryBuilder.should(QueryBuilders.rangeQuery("age").lte(45));//年龄小于等于45 boolQueryBuilder.must(QueryBuilders.matchQuery("sex","女"));//性别为女 searchSourceBuilder.query(boolQueryBuilder); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之布尔查询,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之布尔查询,查询耗时:" + searchResponse.getTook()); hits.forEach(p -> { System.out.println("文档操作,高级查询之布尔查询,查询结果,文档id:" + p.getId() + ",文档原生信息:" + p.getSourceAsString()); }); } /** * 高级查询之日期查询 */ @Test public void restHighSearchDocumentDateQuery() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("birthday"); // rangeQueryBuilder.gt("now-21y");//now-21y:表示当前时间减去21年 rangeQueryBuilder.gt(1027158006000L);//可以直接填时间戳,毫秒值 searchSourceBuilder.query(rangeQueryBuilder); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之日期查询,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之日期查询,查询耗时:" + searchResponse.getTook()); hits.forEach(p -> { System.out.println("文档操作,高级查询之日期查询,查询结果,文档id:" + p.getId() + ",文档原生信息:" + p.getSourceAsString()); }); } /** * 高级查询之多个id查询 */ @Test public void restHighSearchDocumentMoreIdsQuery() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); IdsQueryBuilder idsQueryBuilder = QueryBuilders.idsQuery(); idsQueryBuilder.addIds("1001","1002","1003"); searchSourceBuilder.query(idsQueryBuilder); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之多个id查询,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之多个id查询,查询耗时:" + searchResponse.getTook()); hits.forEach(p -> { System.out.println("文档操作,高级查询之多个id查询,查询结果,文档id:" + p.getId() + ",文档原生信息:" + p.getSourceAsString()); }); } /** * 高级查询之多条件组合查询 */ @Test public void restHighSearchDocumentByConditionsMore() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); //年龄必须是18岁,must 相当于 and boolQueryBuilder.must(QueryBuilders.matchQuery("age",18)); //性别必须是男,must 相当于 and // boolQueryBuilder.must(QueryBuilders.matchQuery("sex","男")); //性别应该是男,should 相当于 or boolQueryBuilder.should(QueryBuilders.matchQuery("sex","男")); //性别应该是女,should 相当于 or boolQueryBuilder.should(QueryBuilders.matchQuery("sex","女")); searchSourceBuilder.query(boolQueryBuilder); searchRequest.source(searchSourceBuilder); //执行查询操作 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之多条件组合查询,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之多条件组合查询,查询耗时:" + searchResponse.getTook()); SearchHit[] result = hits.getHits(); for (SearchHit searchHit: result) { System.out.println("文档操作,高级查询之多条件组合查询,id:" + searchHit.getId() + ",内容:" + searchHit.getSourceAsString()); } } /** * 高级查询之模糊查询 * 1、模糊查询 FuzzyQueryBuilder:QueryBuilders.fuzzyQuery(String name, Object value) 分词模糊查询,通过增加fuzziness 模糊属性,来查询 * ① 输入的 name 会被分词 * ② Fuzziness 参数会指定编辑次数,Fuzziness.ZERO 为绝对匹配,Fuzziness.ONE 为编辑一次(指修改一个字符),Fuzziness.TWO 为编辑两次(指修改两个字符),Fuzziness.AUTO 为默认推荐的方式 * 2、Fuzziness 具体指什么? * QueryBuilders.fuzzyQuery("name", "萧").fuzziness() 方法是用来度量把一个单词转换为另一个单词需要的单字符编辑次数。单字符编辑方式如下: * ① 替换 一个字符到另一个字符: _f_ox -> _b_ox * ② 插入 一个新字符: sic -> sick * ③ 删除 一个字符:: b_l_ack -> back * ④ 换位 调整字符: _st_ar -> _ts_ar * 当然, 一个字符串的单次编辑次数依赖于它的长度。例如:对 hat 进行两次编辑可以得到 mad,所以允许对长度为3的字符串进行两次修改就太过了,Fuzziness 参数可以被设置成 AUTO,结果会在下面的最大编辑距离中: * ① Fuzziness.ZERO:适用于只有 1 或 2 个字符的字符串 * ② Fuzziness.ONE:适用于 3、4或5个字符的字符串 * ③ Fuzziness.TWO:适用于多于5个字符的字符串 * 当然, 你可能发现编辑距离为 2 仍然是太过了,返回的结果好像并没有什么关联,把 Fuzziness 设置为 Fuzziness.ONE ,你可能会获得更好的结果和性能. */ @Test public void restHighSearchDocumentByConditionsLike() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //根据姓名 name 字段进行模糊匹配,不加 fuzziness() 方法表示默认,name 字段中要包含一个 “萧” 字算满足条件 // FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "萧"); //根据姓名 name 字段进行模糊匹配,Fuzziness.AUTO 是默认配置,name 字段中要包含一个 “萧” 字算满足条件 // FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "萧").fuzziness(Fuzziness.AUTO); //根据姓名 name 字段进行模糊匹配,表示 name 字段在不做任何修改的前提下包含一个 “萧” 字则满足条件 // FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "萧").fuzziness(Fuzziness.ZERO); //根据姓名 name 字段进行模糊匹配,表示 name 字段如果修改一个字符能使 name 字段包含一个 “萧炎” 字则满足条件 // FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "萧炎").fuzziness(Fuzziness.ONE); //根据姓名 name 字段进行模糊匹配,表示 name 字段如果修改两个个字符能使 name 字段包含一个 “石漠” 字则满足条件 FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("name", "石漠").fuzziness(Fuzziness.TWO); searchSourceBuilder.query(fuzzyQueryBuilder); searchRequest.source(searchSourceBuilder); //执行查询操作 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之模糊查询,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之模糊查询,查询耗时:" + searchResponse.getTook()); SearchHit[] result = hits.getHits(); for (SearchHit searchHit: result) { System.out.println("文档操作,高级查询之模糊查询,id:" + searchHit.getId() + ",内容:" + searchHit.getSourceAsString()); } } /** * 高级查询之范围查询 */ @Test public void restHighSearchDocumentRange() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //指定根据 age 字段进行区间查询 RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age"); //gte():大于等于,lte():小于等于,gt():大于,lt():小于。 rangeQueryBuilder.gte(18); rangeQueryBuilder.lte(25); searchSourceBuilder.query(rangeQueryBuilder); searchRequest.source(searchSourceBuilder); //执行查询操作 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之范围查询,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之范围查询,查询耗时:" + searchResponse.getTook()); SearchHit[] result = hits.getHits(); for (SearchHit searchHit: result) { System.out.println("文档操作,高级查询之范围查询,id:" + searchHit.getId() + ",内容:" + searchHit.getSourceAsString()); } } /** * 高级查询之高亮查询 */ @Test public void restHighSearchDocumentHighLight() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("sex", "女"); searchSourceBuilder.query(termQueryBuilder); //构建高亮对象 HighlightBuilder highlightBuilder = new HighlightBuilder(); //高亮样式 highlightBuilder.preTags("<font color='red'>");//前缀标签 highlightBuilder.postTags("</font>");//后缀标签 //选择高亮字段 highlightBuilder.field("name"); highlightBuilder.requireFieldMatch(false);//多字段高亮显示时,需要设置为 false highlightBuilder.field("role"); searchSourceBuilder.highlighter(highlightBuilder); searchRequest.source(searchSourceBuilder); //执行查询操作 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之高亮查询,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之高亮查询,查询耗时:" + searchResponse.getTook()); System.out.println("文档操作,高级查询之高亮查询,结果:" + searchResponse); hits.forEach(p -> { System.out.println("文档操作,高级查询之高亮查询,文档原生信息:" + p.getSourceAsString() + ",高亮信息:" + p.getHighlightFields()); }); } /** * 高级查询之查询结果字段过滤 */ @Test public void restHighSearchDocumentResultFieldFilter() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); searchSourceBuilder.query(matchAllQueryBuilder); //要排除的字段 String[] excludes = {"age","role"}; //要展示的字段 String[] includes = {}; searchSourceBuilder.fetchSource(includes,excludes); searchRequest.source(searchSourceBuilder); //执行查询操作 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之查询结果字段过滤,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之查询结果字段过滤,查询耗时:" + searchResponse.getTook()); System.out.println("文档操作,高级查询之查询结果字段过滤,结果:" + searchResponse); SearchHit[] result = hits.getHits(); for (SearchHit searchHit: result) { System.out.println("文档操作,高级查询之查询结果字段过滤,id:" + searchHit.getId() + ",内容:" + searchHit.getSourceAsString()); } } /** * 高级查询之查询结果排序 */ @Test public void restHighSearchDocumentResultOrder() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); searchSourceBuilder.query(matchAllQueryBuilder); //要排除的字段 String[] excludes = {"sex","role"}; //要展示的字段 String[] includes = {"name","age"}; searchSourceBuilder.fetchSource(includes,excludes); //按照年龄字段 age 排序,SortOrder.ASC 升序 // searchSourceBuilder.sort("age", SortOrder.ASC); //按照年龄字段 age 排序,SortOrder.DESC 降序 searchSourceBuilder.sort("age", SortOrder.DESC); searchRequest.source(searchSourceBuilder); //执行查询操作 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之查询结果排序,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之查询结果排序,查询耗时:" + searchResponse.getTook()); System.out.println("文档操作,高级查询之查询结果排序,结果:" + searchResponse); SearchHit[] result = hits.getHits(); for (SearchHit searchHit: result) { System.out.println("文档操作,高级查询之查询结果排序,id:" + searchHit.getId() + ",内容:" + searchHit.getSourceAsString()); } } /** * 高级查询之查询结果去重 */ @Test public void restHighSearchDocumentResultCollapse() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); searchSourceBuilder.query(matchAllQueryBuilder); //年龄 age 字段去重 CardinalityAggregationBuilder cardinalityAggregationBuilderAgeCard = AggregationBuilders.cardinality("ageCard").field("age"); searchSourceBuilder.aggregation(cardinalityAggregationBuilderAgeCard); //角色 role 字段去重 CardinalityAggregationBuilder cardinalityAggregationBuilderRoleCard = AggregationBuilders.cardinality("roleCard").field("role"); searchSourceBuilder.aggregation(cardinalityAggregationBuilderRoleCard); searchRequest.source(searchSourceBuilder); //执行查询操作 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之查询结果去重,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之查询结果去重,查询耗时:" + searchResponse.getTook()); Aggregations aggregations = searchResponse.getAggregations(); ParsedCardinality parsedCardinalityAge = aggregations.get("ageCard"); System.out.println("文档操作,高级查询之查询结果去重,年龄去重后的结果,key:" + parsedCardinalityAge.getName() + ",去重后的数量:" + parsedCardinalityAge.getValue()); ParsedCardinality parsedCardinalityRole = aggregations.get("roleCard"); System.out.println("文档操作,高级查询之查询结果去重,角色去重后的结果,key:" + parsedCardinalityRole.getName() + ",去重后的数量:" + parsedCardinalityRole.getValue()); SearchHit[] result = hits.getHits(); for (SearchHit searchHit: result) { System.out.println("文档操作,高级查询之查询结果去重,id:" + searchHit.getId() + ",内容:" + searchHit.getSourceAsString()); } } /** * 高级查询之聚合查询 * 注意: * ① Text 类型的字段是不允许进行聚合操作的,不能进行分组统计 */ @Test public void restHighSearchDocumentAggregation() throws IOException { SearchRequest searchRequest = new SearchRequest(); searchRequest.indices("user"); SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); //查询年龄 age 的最大值,最大值对应的key为 avgAge AggregationBuilder aggregationBuilderMax = AggregationBuilders.max("maxAge").field("age"); //查询年龄 age 的最小值,最小值对应的key为 avgAge AggregationBuilder aggregationBuilderMin = AggregationBuilders.min("minAge").field("age"); //查询年龄 age 的平均值,平均值对应的key为 avgAge AggregationBuilder aggregationBuilderAvg = AggregationBuilders.avg("avgAge").field("age"); //统计年龄 age 的数量,未去重 AggregationBuilder aggregationBuilderAgeCount = AggregationBuilders.count("ageCount").field("age"); //统计年龄 age 的数量,去重 CardinalityAggregationBuilder cardinalityAggregationBuilderAgeCard = AggregationBuilders.cardinality("ageCard").field("age"); //对年龄 age 字段进行统计,可以统计出最大值、最小值、平均值、数量、和...... StatsAggregationBuilder statsAggregationBuilder = AggregationBuilders.stats("ageStatsResult").field("age"); //根据年龄 age 进行分组,分组后的结果对应的key为 ageGroup AggregationBuilder aggregationBuilderAgeGroup = AggregationBuilders.terms("ageGroup").field("age"); //根据性别 sex 进行分组,分组后的结果对应的key为 sexGroup AggregationBuilder aggregationBuilderSexGroup = AggregationBuilders.terms("sexGroup").field("sex"); searchSourceBuilder.aggregation(aggregationBuilderMax); searchSourceBuilder.aggregation(aggregationBuilderMin); searchSourceBuilder.aggregation(aggregationBuilderAvg); searchSourceBuilder.aggregation(aggregationBuilderAgeCount); searchSourceBuilder.aggregation(cardinalityAggregationBuilderAgeCard); searchSourceBuilder.aggregation(statsAggregationBuilder); searchSourceBuilder.aggregation(aggregationBuilderAgeGroup); searchSourceBuilder.aggregation(aggregationBuilderSexGroup); searchSourceBuilder.sort("age", SortOrder.DESC); searchRequest.source(searchSourceBuilder); //执行查询操作 SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); SearchHits hits = searchResponse.getHits(); System.out.println("文档操作,高级查询之聚合查询,命中数量:" + hits.getTotalHits()); System.out.println("文档操作,高级查询之聚合查询,查询耗时:" + searchResponse.getTook()); Aggregations aggregations = searchResponse.getAggregations(); ParsedMax parsedMax = aggregations.get("maxAge"); System.out.println("文档操作,高级查询之聚合查询,最大值:" + parsedMax.getValue()); ParsedMin parsedMin = aggregations.get("minAge"); System.out.println("文档操作,高级查询之聚合查询,最小值:" + parsedMin.getValue()); ParsedAvg parsedAvg = aggregations.get("avgAge"); System.out.println("文档操作,高级查询之聚合查询,平均值:" + parsedAvg.getValue()); ParsedValueCount parsedAgeCount = aggregations.get("ageCount"); System.out.println("文档操作,高级查询之聚合查询,年龄未去重的数量:" + parsedAgeCount.getValue()); ParsedCardinality parsedCardinalityAge = aggregations.get("ageCard"); System.out.println("文档操作,高级查询之聚合查询,年龄去重后的数量:" + parsedCardinalityAge.getValue()); ParsedStats ageStatsResult = aggregations.get("ageStatsResult"); System.out.println("文档操作,高级查询之聚合查询,对年龄进行统计后的结果,统计个数(未去重):" + ageStatsResult.getCount()); System.out.println("文档操作,高级查询之聚合查询,对年龄进行统计后的结果,年龄平均值:" + ageStatsResult.getAvg()); System.out.println("文档操作,高级查询之聚合查询,对年龄进行统计后的结果,年龄最大值:" + ageStatsResult.getMax()); System.out.println("文档操作,高级查询之聚合查询,对年龄进行统计后的结果,年龄最小值:" + ageStatsResult.getMin()); System.out.println("文档操作,高级查询之聚合查询,对年龄进行统计后的结果,年龄之和:" + ageStatsResult.getSum()); System.out.println("############# 年龄分组结果输出部分 ###############"); Terms termsAgeGroup = aggregations.get("ageGroup"); for (Terms.Bucket bucket : termsAgeGroup.getBuckets()) { System.out.println("文档操作,高级查询之聚合查询,年龄分组,key:" + bucket.getKeyAsString() + ",value:" + bucket.getDocCount()); } System.out.println("############# 年龄分组结果输出部分 ###############"); System.out.println("------------- 性别分组结果输出部分 ----------------"); Terms termsSexGroup = aggregations.get("sexGroup"); for (Terms.Bucket bucket : termsSexGroup.getBuckets()) { System.out.println("文档操作,高级查询之聚合查询,性别分组,key:" + bucket.getKeyAsString() + ",value:" + bucket.getDocCount()); } System.out.println("------------- 性别分组结果输出部分 ----------------"); SearchHit[] result = hits.getHits(); for (SearchHit searchHit: result) { System.out.println("文档操作,高级查询之聚合查询,id:" + searchHit.getId() + ",内容:" + searchHit.getSourceAsString()); } } }
5、使用 ElasticsearchRepository 操作es索引库
1)实体类 Product
package com.test.elasticsearch.entity; import com.fasterxml.jackson.annotation.JsonFormat; import java.util.Date; import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Document; import java.io.Serializable; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; /** * indexName 指定索引名 * shards 指定分片(无特殊配置,默认即可) * replicas 指定副本集(无特殊配置,默认即可) */ @Data @Document(indexName = "product", shards = 1, replicas = 1) public class Product implements Serializable { private static final long serialVersionUID = 551589397625941751L; @Id private Long id;//商品唯一标识 @Field(type = FieldType.Text) private String name;//商品名称 @Field(type = FieldType.Keyword) private String category;//商品分类 @Field(type = FieldType.Double) private Double price;//商品价格 @Field(type = FieldType.Date, format = DateFormat.custom,pattern = "yyyy-MM-dd") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd",timezone="GMT+8") private Date upTime;//商品上架时间 @Field(type = FieldType.Date, format = DateFormat.date_optional_time) @JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZ",timezone="GMT+8") private Date downTime;//商品下架时间 @Field(type = FieldType.Keyword, index = false)//index = false 表示该字段不能作为查询条件 private String images;//图片地址 }
2)ProductRepository
package com.test.elasticsearch.mapper; import com.test.elasticsearch.entity.Product; import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; /** * ElasticsearchRepository<Product, String> 中的 Product 为索引对应的实体类,String 为索引的id数值类型 */ @Repository public interface ProductRepository extends ElasticsearchRepository<Product, String> { /** * 根据类型名称查询商品列表 * @param category 品牌名称 * @return */ List<Product> findProductsByCategory(String category); /** * 根据商品id查询对应的商品 * @param id 商品id * @return */ Product findProductById(Long id); /** * 查询名称中包含某个关键字的所有商品 * @param nameKeyword 会进行完整匹配,不会被分词 * @return */ List<Product> findProductsByNameContains(String nameKeyword); /** * 查询商品id在[id1,id2]区间内的所有商品 * @param id1 商品id * @param id2 商品id * @param pageable 分页参数 * @return */ Page<Product> findProductsByIdBetween(Long id1,Long id2, Pageable pageable); }
3)测试类 SpringDataESIndexRepositoryTest
package com.test.elasticsearch; import com.test.elasticsearch.entity.Product; import com.test.elasticsearch.mapper.ProductRepository; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Optional; import javax.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * Elasticsearch 测试类 * 使用 ElasticsearchRepository 操作es索引库 */ @Slf4j @RunWith(SpringJUnit4ClassRunner.class)//不加这个注解 userService 就无法注入 @SpringBootTest public class SpringDataESIndexRepositoryTest { @Resource private ProductRepository productRepository; /** * 功能:ElasticsearchRepository测试 -- 查询全部文档 * 方法: * Iterable<T> findAll() */ @Test public void queryDocumentAll() { List<Product> list = new ArrayList<>(); Iterable<Product> iterable = productRepository.findAll(); Iterator<Product> iterator = iterable.iterator(); while (iterator.hasNext()) { list.add(iterator.next()); } System.out.println("ElasticsearchRepository测试,findAll(),查询全部文档,结果:" + list); } /** * 功能:ElasticsearchRepository测试 -- 添加文档 * 方法: * <S extends T> S save(S var1):参数为对象 * 备注: * ① 文档 id 存在则是更新,否则为新增 */ @Test public void addDocument() { Product product = new Product(); product.setId(1001L); product.setName("小米 S12 PRO"); product.setCategory("手机"); product.setPrice(4866.88); product.setUpTime(new Date(1634524735000L)); product.setDownTime(new Date(2549414335000L)); product.setImages("F:\\pictures\\yy测试下载图片.jpg"); Product saveResult = productRepository.save(product); System.out.println("ElasticsearchRepository测试,save(),添加文档,结果:" + saveResult); } /** * 功能:ElasticsearchRepository测试 -- 更新文档 * 方法: * <S extends T> S save(S var1):参数为对象 * 备注: * ① 文档 id 存在则是更新,否则为新增 */ @Test public void updateDocument() { Product product = new Product(); product.setId(1001L); product.setName("小米 S12 PRO"); product.setCategory("手机"); product.setPrice(4866.88); product.setUpTime(new Date(1634524735000L)); product.setDownTime(new Date(1981420735000L)); product.setImages("F:\\pictures\\yy测试下载图片.jpg"); Product saveResult = productRepository.save(product); System.out.println("ElasticsearchRepository测试,save(),更新文档,结果:" + saveResult); } /** * 功能:ElasticsearchRepository测试 -- 批量添加文档 * 方法: * <S extends T> Iterable<S> saveAll(Iterable<S> var1):参数可以是 List<Product> */ @Test public void addDocumentBatch() { List<Product> list = new ArrayList<>(); Product product01 = new Product(); product01.setId(1002L); product01.setName("小米 S11"); product01.setCategory("手机"); product01.setPrice(3966.88); product01.setUpTime(new Date(1602729535000L)); product01.setImages("F:\\pictures\\yy测试下载图片.jpg"); list.add(product01); Product product02 = new Product(); product02.setId(1003L); product02.setName("比亚迪 汉EV"); product02.setCategory("汽车"); product02.setPrice(268888.00); product02.setUpTime(new Date(1623724735000L)); list.add(product02); Product product03 = new Product(); product03.setId(1004L); product03.setName("华为 Mate50 PRO"); product03.setCategory("手机"); product03.setPrice(6999.00); product03.setUpTime(new Date(1665801535000L)); list.add(product03); Iterable<Product> iterable = productRepository.saveAll(list); Iterator<Product> iterator = iterable.iterator(); List<Product> saveAllResponse = new ArrayList<>(); while (iterator.hasNext()) { saveAllResponse.add(iterator.next()); } System.out.println("ElasticsearchRepository测试,saveAll(),批量添加文档,结果:" + saveAllResponse); } /** * 功能:ElasticsearchRepository测试 -- 删除文档 * 方法: * void delete(T var1):方法中传入的对象必须包含 id 属性值,如果 id 属性值为null,是无法删除的 */ @Test public void deleteDocument() { Product product = new Product(); product.setId(1001L); product.setName("小米 S12 PRO"); product.setCategory("手机"); System.out.println("ElasticsearchRepository测试,delete(),删除文档,删除前是否存在:" + productRepository.existsById(product.getId() == null ? "1001" : product.getId().toString())); productRepository.delete(product); System.out.println("ElasticsearchRepository测试,delete(),删除文档,删除后是否存在:" + productRepository.existsById(product.getId() == null ? "1001" : product.getId().toString())); } /** * 功能:ElasticsearchRepository测试 -- 根据文档id删除文档 * 方法: * void deleteById(ID var1):这里 id 要是 String 类型 */ @Test public void deleteDocumentById() { productRepository.deleteById("1001"); System.out.println("ElasticsearchRepository测试,deleteById(),根据文档id删除文档,成功删除!!!"); } /** * 功能:ElasticsearchRepository测试 -- 批量删除文档 * 方法: * void deleteAll(Iterable<? extends T> var1):传入的对象集合中的每个对象都必须包含 id 属性值,如果 id 属性值为null,是无法删除的 */ @Test public void deleteDocumentBatch() { List<Product> list = new ArrayList<>(); Product product01 = new Product(); product01.setId(1002L); list.add(product01); Product product02 = new Product(); // product02.setId(1003L); product02.setName("比亚迪 汉EV"); product02.setCategory("汽车"); list.add(product02); Product product03 = new Product(); product03.setId(1004L); list.add(product03); productRepository.deleteAll(list); System.out.println("ElasticsearchRepository测试,deleteAll(),批量删除文档,删除成功!!!"); } /** * 功能:ElasticsearchRepository测试 -- 根据文档id查询文档 * 方法: * Optional<T> findById(ID var1):id 要是 String 类型 */ @Test public void queryDocumentById() { Optional<Product> optional = productRepository.findById("1001"); if (optional.isPresent()) { Product product = optional.get(); System.out.println("ElasticsearchRepository测试,findById(),根据文档id查询文档,结果:" + product); } else { System.out.println("ElasticsearchRepository测试,findById(),根据文档id查询文档,没有查到结果:" + optional); } } /** * 功能:ElasticsearchRepository测试 -- 根据多个文档id查询文档 * 方法: * Iterable<T> findAllById(Iterable<ID> var1):参数可以是一个 List<String> */ @Test public void queryDocumentAllById() { List<Product> result = new ArrayList<>(); List<String> productIds = Arrays.asList("1001","1003"); Iterable<Product> iterable = productRepository.findAllById(productIds); Iterator<Product> iterator = iterable.iterator(); while (iterator.hasNext()) { result.add(iterator.next()); } System.out.println("ElasticsearchRepository测试,findAllById(),根据多个文档id查询文档,结果:" + result); } /** * 功能:ElasticsearchRepository自定义查询方法测试 -- 根据类型名称查询商品列表 * 方法: * List<Product> findProductsByCategory(String category):自定义方法 */ @Test public void findProductsByCategory() { List<Product> products = productRepository.findProductsByCategory("手机"); System.out.println("ElasticsearchRepository自定义查询方法测试,findProductsByCategory(),根据类型名称查询商品列表,结果:" + products); } /** * 功能:ElasticsearchRepository自定义查询方法测试 -- 根据商品id查询对应的商品 * 方法: * Product findProductById(Long id):自定义方法 */ @Test public void queryDocumentByCategory() { Product product = productRepository.findProductById(1001L); System.out.println("ElasticsearchRepository自定义查询方法测试,findProductById(),根据商品id查询对应的商品,结果:" + product); } /** * 功能:ElasticsearchRepository自定义查询方法测试 -- 查询名称中包含某个关键字的所有商品 * 方法: * List<Product> findProductsByNameContains(String nameKeyword):自定义方法 * 备注: * ① 这里的查询条件 nameKeyword 不会被分词,查询条件是完全匹配 */ @Test public void findProductsByNameContains() { List<Product> products01 = productRepository.findProductsByNameContains("迪"); System.out.println("ElasticsearchRepository自定义查询方法测试,findProductsByNameContains(),查询名称中包含某个关键字的所有商品,结果:" + products01); List<Product> products02 = productRepository.findProductsByNameContains("比亚"); System.out.println("ElasticsearchRepository自定义查询方法测试,findProductsByNameContains(),查询名称中包含某个关键字的所有商品,结果:" + products02); List<Product> products03 = productRepository.findProductsByNameContains("比亚迪"); System.out.println("ElasticsearchRepository自定义查询方法测试,findProductsByNameContains(),查询名称中包含某个关键字的所有商品,结果:" + products03); } /** * 功能:ElasticsearchRepository自定义查询方法测试 -- 查询商品id在[id1,id2]区间内的所有商品 * 方法: * Page<Product> findProductsByIdBetween(Long id1,Long id2, Pageable pageable):自定义方法 * 备注: * ① id1、id2 被包含在内 * ② Pageable 分页的起始页是 第0页,不是 第1页 */ @Test public void findProductsByIdBetween() { Pageable pageable = PageRequest.of(0, 10); Page<Product> page = productRepository.findProductsByIdBetween(1001L, 1004L, pageable); List<Product> products = page.getContent(); System.out.println("ElasticsearchRepository自定义查询方法测试,findProductsByIdBetween(),查询商品id在[id1,id2]区间内的所有商品,结果:" + products); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。