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>
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 | {} | 如果某个字段需要被忽略 |
type:字段类型,是是枚举:FieldType,可以是text、long、short、date、integer、object等
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分词器
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 }
demo源码:https://github.com/Rolayer/springboot2.x-electricsearch7.0.1