pom
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <parent> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-parent</artifactId> 8 <version>2.2.5.RELEASE</version> 9 <relativePath/> <!-- lookup parent from repository --> 10 </parent> 11 <groupId>com.example</groupId> 12 <artifactId>demo</artifactId> 13 <version>0.0.1-SNAPSHOT</version> 14 <name>demo</name> 15 <description>Demo project for Spring Boot</description> 16 17 <properties> 18 <java.version>1.8</java.version> 19 </properties> 20 <repositories> 21 <repository> 22 <id>spring-milestones</id> 23 <name>Spring Milestones</name> 24 <url>https://repo.spring.io/milestone</url> 25 </repository> 26 </repositories> 27 <dependencies> 28 <dependency> 29 <groupId>com.alibaba</groupId> 30 <artifactId>fastjson</artifactId> 31 <version>1.2.8</version> 32 </dependency> 33 <dependency> 34 <groupId>org.projectlombok</groupId> 35 <artifactId>lombok</artifactId> 36 <optional>true</optional> 37 </dependency> 38 <dependency> 39 <groupId>org.springframework.boot</groupId> 40 <artifactId>spring-boot-starter-data-elasticsearch</artifactId> 41 </dependency> 42 <dependency> 43 <groupId>org.springframework.boot</groupId> 44 <artifactId>spring-boot-starter-web</artifactId> 45 </dependency> 46 47 <dependency> 48 <groupId>org.springframework.boot</groupId> 49 <artifactId>spring-boot-starter-test</artifactId> 50 <scope>test</scope> 51 <exclusions> 52 <exclusion> 53 <groupId>org.junit.vintage</groupId> 54 <artifactId>junit-vintage-engine</artifactId> 55 </exclusion> 56 </exclusions> 57 </dependency> 58 </dependencies> 59 <build> 60 <plugins> 61 <plugin> 62 <groupId>org.springframework.boot</groupId> 63 <artifactId>spring-boot-maven-plugin</artifactId> 64 </plugin> 65 </plugins> 66 </build> 67 68 </project>pom
Goods.java
1 package com.example.demo; 2 3 import lombok.AllArgsConstructor; 4 import lombok.NoArgsConstructor; 5 import org.springframework.data.annotation.Id; 6 import org.springframework.data.elasticsearch.annotations.Document; 7 import org.springframework.data.elasticsearch.annotations.Field; 8 import org.springframework.data.elasticsearch.annotations.FieldType; 9 10 /** 11 * @Author: pengbenlei 12 * @Date: 2020/3/9 11:39 13 * @Description: 14 */ 15 @NoArgsConstructor 16 @AllArgsConstructor 17 @Document(indexName = "item",type = "docs", shards = 1, replicas = 0) 18 public class Goods { 19 @Id 20 private Long id; 21 @Field(type = FieldType.Text, analyzer = "ik_max_word") 22 private String title; //标题 23 @Field(type = FieldType.Keyword) 24 private String category;// 分类 25 @Field(type = FieldType.Keyword) 26 private String brand; // 品牌 27 @Field(type = FieldType.Double) 28 private Double price; // 价格 29 @Field(index = false, type = FieldType.Keyword) 30 private String images; // 图片地址 31 32 public Goods(long id, String title, String category, String brand, double price, String images) { 33 this.id=id; 34 this.title=title; 35 this.category=category; 36 this.brand=brand; 37 this.price=price; 38 this.images=images; 39 } 40 41 public Long getId() { 42 return id; 43 } 44 45 public void setId(Long id) { 46 this.id = id; 47 } 48 49 public String getTitle() { 50 return title; 51 } 52 53 public void setTitle(String title) { 54 this.title = title; 55 } 56 57 public String getCategory() { 58 return category; 59 } 60 61 public void setCategory(String category) { 62 this.category = category; 63 } 64 65 public String getBrand() { 66 return brand; 67 } 68 69 public void setBrand(String brand) { 70 this.brand = brand; 71 } 72 73 public Double getPrice() { 74 return price; 75 } 76 77 public void setPrice(Double price) { 78 this.price = price; 79 } 80 81 public String getImages() { 82 return images; 83 } 84 85 public void setImages(String images) { 86 this.images = images; 87 } 88 }商品类
商品类中有几个注解,解释一下:
1、@Document (相当于Hibernate实体的@Entity/@Table)(必写),加上了@Document注解之后,默认情况下这个实体中所有的属性都会被建立索引、并且分词。
类型 | 属性名 | 默认值 | 说明 | |
String | indexName |
|
||
String | type | "" |
|
|
short | shards | 5 |
|
|
short | replica | 1 |
|
|
String | refreshInterval | "1s" | 刷新间隔 | |
String | indexStoreType | "fs" | 索引文件储存 |
2、@Id (相当于Hibernate实体的主键@Id注解)(必写)
3、@Field (相当于Hibernate实体的@Column注解),@Field默认是可以不加的,默认所有属性都会添加到ES中。加上@Field之后,@document默认把所有字段加上索引失效,只有加@Field 才会被索引(同时也看设置索引的属性是否为no)
Field 参数对照表
类型 | 属性名 | 默认值 | 说明 |
FieldType | type | FieldType.Auto | 自动检测属性的类型 |
FieldIndex | index | FieldIndex.analyzed | 默认情况下粉尘 |
boolean | store | false | 默认情况下不存储原文 |
String | searchAnalyzer | "" | 指定字段搜索是使用的分词器 |
String | indexAnalyzer | "" | 指定字段建立索引时指定的分词器 |
String[] | ignoreFields | {} | 如果某个字段需要被忽略 |
text:存储数据时候,会自动分词,并生成索引
keyword:存储数据时候,不会分词建立索引
Numerical:数值类型,分两类
基本数据类型:long、interger、short、byte、double、float、half_float
浮点数的高精度类型:scaled_float
需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
Date:日期类型
elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。
index:是否索引,布尔类型,默认是true
store:是否存储,布尔类型,默认是false
analyzer:分词器名称,这里的ik_max_word即使用ik分词器
GoodsRepository.java
1 package com.example.demo; 2 3 import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; 4 import org.springframework.stereotype.Component; 5 6 import java.util.List; 7 8 /** 9 * @Author: pengbenlei 10 * @Date: 2020/3/9 11:40 11 * @Description: 12 */ 13 @Component 14 public interface GoodsRepository extends ElasticsearchRepository<Goods,Long> { 15 16 /** 17 * 根据价格区间查询 18 * @param price1 19 * @param price2 20 * @return 21 */ 22 List<Goods> findByPriceBetween(double price1, double price2); 23 24 Goods findByTitle(String title); 25 }商品仓接口
其中有两个方法,没有实现,确能正常执行,是因为方法名称中的Between和findBy关键字,es会智能根据关键字和字段名称拼接查询,对照表如下:
关键字 | 方法名称实例 | ||
And | findByNameAndPrice | } | |
Or | findByNameOrPrice | ||
Is | findByName | ||
Not | findByNameNot | ||
Between | findByPriceBetween | ||
LessThanEqual | findByPriceLessThan | ||
GreaterThanEqual | findByPriceGreaterThan | ||
Before | findByPriceBefore | ||
After | findByPriceAfter | ||
Like | findByNameLike | ||
StartingWith | findByNameStartingWith | ||
EndingWith | findByNameEndingWith | ||
Contains/Containing | findByNameContaining | ||
In |
|
||
NotIn |
|
||
Near |
|
||
True | findByAvailableTrue | ||
False | findByAvailableFalse | ||
OrderBy | findByAvailableTrueOrderByNameDesc |
基础测试类:
1 package com.example.demo; 2 3 import com.alibaba.fastjson.JSON; 4 import org.elasticsearch.action.get.MultiGetRequest; 5 import org.elasticsearch.client.Client; 6 import org.elasticsearch.client.RestHighLevelClient; 7 import org.elasticsearch.index.query.MatchQueryBuilder; 8 import org.elasticsearch.index.query.QueryBuilders; 9 import org.elasticsearch.search.aggregations.AggregationBuilders; 10 import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; 11 import org.elasticsearch.search.aggregations.metrics.avg.InternalAvg; 12 import org.elasticsearch.search.sort.SortBuilders; 13 import org.elasticsearch.search.sort.SortOrder; 14 import org.junit.jupiter.api.Test; 15 import org.springframework.beans.factory.annotation.Autowired; 16 import org.springframework.boot.test.context.SpringBootTest; 17 import org.springframework.data.domain.Page; 18 import org.springframework.data.domain.PageRequest; 19 import org.springframework.data.domain.Sort; 20 import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; 21 import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage; 22 import org.springframework.data.elasticsearch.core.query.FetchSourceFilter; 23 import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 28 @SpringBootTest 29 class DemoApplicationTests { 30 @Autowired 31 GoodsRepository goodsRepository; 32 @Test 33 void contextLoads() { 34 } 35 // @Autowired 36 // private ElasticsearchTemplate elasticsearchTemplate; 37 38 39 40 41 @Test 42 public void addDocument() { 43 Goods goods = new Goods(1L, "大米1S", "手机", 44 "大米", 3499.00, "https://img13.360buyimg.com/n1/s450x450_jfs/t1/79993/29/9874/153231/5d7809f4E8f387bff/1dc9e1b6b262f0fb.jpg"); 45 Goods goods0 = goodsRepository.save(goods); 46 System.out.println(JSON.toJSONString(goods0)); 47 } 48 49 /** 50 * 批量新增 51 */ 52 @Test 53 public void createDocumentList() { 54 List<Goods> list = new ArrayList<>(); 55 list.add(new Goods(2L, "坚果手机R1", " 手机", "锤子", 3699.00, "http://image.leyou.com/123.jpg")); 56 list.add(new Goods(3L, "华为META10", " 手机", "华为", 4499.00, "http://image.leyou.com/3.jpg")); 57 // 接收对象集合,实现批量新增 58 goodsRepository.saveAll(list); 59 } 60 61 @Test 62 public void findDocument() { 63 // 查询全部,并安装价格降序排序 64 Iterable<Goods> goodsIterable = this.goodsRepository.findAll(Sort.by(Sort.Direction.DESC, "price")); 65 goodsIterable.forEach(goods -> System.out.println(JSON.toJSONString(goods))); 66 } 67 68 @Test 69 public void indexList() { 70 List<Goods> list = new ArrayList<>(); 71 list.add(new Goods(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.leyou.com/13123.jpg")); 72 list.add(new Goods(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.leyou.com/13123.jpg")); 73 list.add(new Goods(3L, "华为META10", "手机", "华为", 4499.00, "http://image.leyou.com/13123.jpg")); 74 list.add(new Goods(4L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.leyou.com/13123.jpg")); 75 list.add(new Goods(5L, "荣耀V10", "手机", "华为", 2799.00, "http://image.leyou.com/13123.jpg")); 76 // 接收对象集合,实现批量新增 77 goodsRepository.saveAll(list); 78 } 79 80 @Test 81 public void queryByPriceBetween(){ 82 List<Goods> list = this.goodsRepository.findByPriceBetween(2000.00, 3500.00); 83 for (Goods goods: list) { 84 System.out.println(goods); 85 } 86 } 87 @Test 88 public void testQuery(){ 89 MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("id", "1"); 90 Iterable<Goods> items =goodsRepository.search(queryBuilder); 91 for (Goods item : items) { 92 System.out.println(JSON.toJSONString(item)); 93 } 94 } 95 96 @Test 97 void findByTitle() 98 { 99 Goods goods= goodsRepository.findByTitle("小米Mix2S"); 100 System.out.println(JSON.toJSONString(goods)); 101 } 102 103 104 @Test 105 public void testNativeQuery(){ 106 // 构建查询条件 107 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); 108 // 添加基本的分词查询 109 queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机")); 110 111 // 初始化分页参数 112 int page = 0; 113 int size = 20; 114 // 设置分页参数 115 queryBuilder.withPageable(PageRequest.of(page, size)); 116 117 // 执行搜索,获取结果 118 Page<Goods> items = goodsRepository.search(queryBuilder.build()); 119 // 打印总条数 120 System.out.println(items.getTotalElements()); 121 // 打印总页数 122 System.out.println(items.getTotalPages()); 123 // 每页大小 124 System.out.println(items.getSize()); 125 // 当前页 126 System.out.println(items.getNumber()); 127 for (Goods item : items) { 128 System.out.println(JSON.toJSONString(item)); 129 } 130 } 131 @Test 132 public void testSort(){ 133 // 构建查询条件 134 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); 135 // 添加基本的分词查询 136 queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机")); 137 138 // 排序 139 queryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.ASC)); 140 141 // 执行搜索,获取结果 142 Page<Goods> items = goodsRepository.search(queryBuilder.build()); 143 // 打印总条数 144 System.out.println(items.getTotalElements()); 145 for (Goods item : items) { 146 System.out.println(JSON.toJSONString(item)); 147 } 148 } 149 @Test 150 /** 151 * 按照品牌brand进行分组 统计各品牌的总数 152 * */ 153 public void testAgg(){ 154 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); 155 // 不查询任何结果 156 queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); 157 // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand 158 queryBuilder.addAggregation( 159 AggregationBuilders.terms("brands").field("brand")); 160 // 2、查询,需要把结果强转为AggregatedPage类型 161 AggregatedPage<Goods> aggPage = (AggregatedPage<Goods>) goodsRepository.search(queryBuilder.build()); 162 // 3、解析 163 // 3.1、从结果中取出名为brands的那个聚合, 164 // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型 165 StringTerms agg = (StringTerms) aggPage.getAggregation("brands"); 166 // 3.2、获取桶 167 List<StringTerms.Bucket> buckets = agg.getBuckets(); 168 // 3.3、遍历 169 for (StringTerms.Bucket bucket : buckets) { 170 // 3.4、获取桶中的key,即品牌名称 171 System.out.println(bucket.getKeyAsString()); 172 // 3.5、获取桶中的文档数量 173 System.out.println(bucket.getDocCount()); 174 } 175 } 176 @Test 177 /** 178 * 嵌套聚合,求平均值 179 * */ 180 public void testSubAgg(){ 181 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); 182 // 不查询任何结果 183 queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); 184 // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand 185 queryBuilder.addAggregation( 186 AggregationBuilders.terms("brands").field("brand") 187 .subAggregation(AggregationBuilders.avg("priceAvg").field("price")) // 在品牌聚合桶内进行嵌套聚合,求平均值 188 ); 189 // 2、查询,需要把结果强转为AggregatedPage类型 190 AggregatedPage<Goods> aggPage = (AggregatedPage<Goods>) goodsRepository.search(queryBuilder.build()); 191 // 3、解析 192 // 3.1、从结果中取出名为brands的那个聚合, 193 // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型 194 StringTerms agg = (StringTerms) aggPage.getAggregation("brands"); 195 // 3.2、获取桶 196 List<StringTerms.Bucket> buckets = agg.getBuckets(); 197 // 3.3、遍历 198 for (StringTerms.Bucket bucket : buckets) { 199 // 3.4、获取桶中的key,即品牌名称 3.5、获取桶中的文档数量 200 System.out.println(bucket.getKeyAsString() + ",共" + bucket.getDocCount() + "台"); 201 202 // 3.6.获取子聚合结果: 203 InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("priceAvg"); 204 System.out.println("平均售价:" + avg.getValue()); 205 } 206 } 207 208 }功能测试类