Spring Data Elasticsearch Demo
Spring Data Elasticsearch
文章目录
环境
Elasticsearch7.8.0
SpringBoot2.3.6
1 创建测试环境
1.1 添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.iforeverhz</groupId>
<artifactId>es-spring</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
1.2 添加配置
@Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() {
RestHighLevelClient restHighLevelClient = new RestHighLevelClient(
RestClient.builder(new HttpHost("localhost",9200))
);
return restHighLevelClient;
}
}
2 实体类及注解
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "goods" ,shards = 3,replicas = 1 )
public class Item {
@Field(type = FieldType.Long)
@Id
Long id;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
String title; //标题
@Field(type = FieldType.Keyword)
String category;// 分类
@Field(type = FieldType.Keyword)
String brand; // 品牌
@Field(type = FieldType.Double)
Double price; // 价格
@Field(index = false, type = FieldType.Keyword )
String images; // 图片地址
}
Spring Data通过注解来声明字段的映射属性,有下面的三个注解:
@Document
作用在类,标记实体类为文档对象,一般有两个属性- indexName:对应索引库名称
- shards:分片数量,默认1
- replicas:副本数量,默认1
@Id
作用在成员变量,标记一个字段作为id主键@Field
作用在成员变量,标记为文档的字段,并指定字段映射属性:- type:字段类型,取值是枚举:FieldType
- index:是否索引,布尔类型,默认是true
- store:是否存储,布尔类型,默认是false
- analyzer:分词器名称
3 创建Repository
@Repository
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
/**
* 根据价格区间查询
* @param price1
* @param price2
* @return
*/
List<Item> findByPriceBetween(double price1, double price2);
}
ElasticsearchRepository继承关系
4 索引操作
4.1 创建索引
public void createIndex() {
// 创建索引库1 方法过时
// template.createIndex(Item.class);
// 映射关系 方法过时
//template.putMapping(Item.class);
// 创建索引2
IndexOperations indexOperations = template.indexOps(Item.class);
if (indexOperations.exists()) {
System.out.println("索引已存在");
return;
}
indexOperations.create();
System.out.println("索引创建成功");
}
4.2 删除索引
public void deleteIndex() {
//System.out.println(template.deleteIndex(Item.class));
IndexOperations indexOperations = template.indexOps(Item.class);
if (indexOperations.exists()) {
if (indexOperations.delete()) {
System.out.println("索引删除成功");
}else {
System.out.println("索引删除失败");
}
return;
}
System.out.println("索引不存在");
}
5 文档操作
5.1 新增文档
public void addDoc() {
Item item = new Item(1L, "小米手机7", "手机", "小米", 4299.00, "http://image.iforeverhz.com/13123.jpg");
Item save = repository.save(item);
System.out.println(save);
}
5.2 批量新增文档
public void addBulkDoc() {
List<Item> list = new ArrayList<>();
list.add(new Item(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.iforeverhz.com/13123.jpg"));
list.add(new Item(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.iforeverhz.com/13123.jpg"));
list.add(new Item(3L, "华为META10", "手机", "华为", 4499.00, "http://image.iforeverhz.com/13123.jpg"));
list.add(new Item(4L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.iforeverhz.com/13123.jpg"));
list.add(new Item(5L, "荣耀V10", "手机", "华为", 2799.00, "http://image.iforeverhz.com/13123.jpg"));
list.add(new Item(6L, "小米手机7", "手机", "小米", 3299.00, "http://image.iforeverhz.com/13123.jpg"));
list.add(new Item(7L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.iforeverhz.com/13123.jpg"));
list.add(new Item(8L, "华为META10", "手机", "华为", 4499.00, "http://image.iforeverhz.com/13123.jpg"));
list.add(new Item(9L, "小米Mix2S", "手机", "小米", 4299.00, "http://image.iforeverhz.com/13123.jpg"));
list.add(new Item(10L, "荣耀V10", "手机", "华为", 2799.00, "http://image.iforeverhz.com/13123.jpg"));
// 接收对象集合,实现批量新增
repository.saveAll(list);
}
5.3 修改文档
public void updateDoc() {
Item item = new Item(1L, "小米手机7", "手机", "小米", 5299.00, "http://image.iforeverhz.com/13123.jpg");
Item save = repository.save(item);
System.out.println(save);
}
5.4 删除文档
public void delete() {
// 根据id删除
repository.deleteById(1L);
// 根据item删除doc
Item item = new Item();
item.setId(2l);
repository.delete(item);
// 删除所有
repository.deleteAll();
// 删除指定的doc
List<Item> list = new ArrayList<>();
list.add(new Item(1L, "小米手机7", "手机", "小米", 3299.00, "http://image.iforeverhz.com/13123.jpg"));
list.add(new Item(2L, "坚果手机R1", "手机", "锤子", 3699.00, "http://image.iforeverhz.com/13123.jpg"));
repository.deleteAll(() -> list.stream().iterator());
}
5.5 基本查询
5.5.1 查询所有
public void findAll() {
// 查询所有
Iterable<Item> items = repository.findAll();
items.forEach(item -> System.out.println(item));
// 查询所有并按照价格升序排序
Iterable<Item> items1 = repository.findAll(Sort.by(Sort.Direction.ASC, "price"));
// 查询所有并按照价格降序排序
Iterable<Item> items2 = repository.findAll(Sort.by(Sort.Direction.DESC, "price"));
items1.forEach(System.out::println);
items2.forEach(System.out::println);
// 查询所有并分页
int page = 0;
int size = 3;
Page<Item> itemPage = repository.findAll(PageRequest.of(page, size));
// 解析分页结果
// 分页结果 第1页
List<Item> items4 = itemPage.getContent();
// 总页数
int totalPages = itemPage.getTotalPages();
// 总数
long totalElements = itemPage.getTotalElements();
System.out.println(totalElements+"==="+totalPages);
items4.forEach(System.out::println);
}
5.5.2 根据id查询
public void findById() {
Optional<Item> optionalItem = repository.findById(1L);
optionalItem.ifPresent(item -> System.out.println(item));
}
5.5.3 自定义方法查询
Spring Data 的另一个强大功能,是根据方法名称自动实现功能。
比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。
当然,方法名称要符合一定的约定:
Keyword | Sample | Elasticsearch Query String |
---|---|---|
And | findByNameAndPrice | {"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Or | findByNameOrPrice | {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}} |
Is | findByName | {"bool" : {"must" : {"field" : {"name" : "?"}}}} |
Not | findByNameNot | {"bool" : {"must_not" : {"field" : {"name" : "?"}}}} |
Between | findByPriceBetween | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
LessThanEqual | findByPriceLessThan | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
GreaterThanEqual | findByPriceGreaterThan | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Before | findByPriceBefore | {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}} |
After | findByPriceAfter | {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}} |
Like | findByNameLike | {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
StartingWith | findByNameStartingWith | {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}} |
EndingWith | findByNameEndingWith | {"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}} |
Contains/Containing | findByNameContaining | {"bool" : {"must" : {"field" : {"name" : {"query" : "**?**","analyze_wildcard" : true}}}}} |
In | findByNameIn(Collection<String>names) | {"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}} |
NotIn | findByNameNotIn(Collection<String>names) | {"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}} |
Near | findByStoreNear | Not Supported Yet ! |
True | findByAvailableTrue | {"bool" : {"must" : {"field" : {"available" : true}}}} |
False | findByAvailableFalse | {"bool" : {"must" : {"field" : {"available" : false}}}} |
OrderBy | findByAvailableTrueOrderByNameDesc | {"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}} |
例如,我们来按照价格区间查询,定义这样的一个方法:
@Repository
public interface ItemRepository extends ElasticsearchRepository<Item,Long> {
/**
* 根据价格区间查询
* @param price1
* @param price2
* @return
*/
List<Item> findByPriceBetween(double price1, double price2);
}
public void query() {
List<Item> items = repository.findByPriceBetween(2000, 3500D);
items.forEach(System.out::println);
}
5.6 高级查询
5.6.1基本查询
public void termQuery() {
// 词条查询
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brand", "小米");
// 执行查询
Iterable<Item> items = repository.search(termQueryBuilder);
items.forEach(System.out::println);
}
QueryBuilders提供了大量的静态方法,用于生成各种不同类型的查询对象,例如:词条、模糊、通配符等QueryBuilder对象。
5.6.2 分页&排序&结果过滤查询
使用repository
public void testQuery() {
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 结果过滤
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "title", "price"}, null));
// 添加查询条件
queryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米手机")); // title == "小米手机"
// 排序
queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC)); // 根据价格降序排序
// 分页
queryBuilder.withPageable(PageRequest.of(0, 2));
// 方法过时
Page<Item> items = repository.search(queryBuilder.build());
// 总条数
long totalElements = items.getTotalElements();
// 总页数
int totalPages = items.getTotalPages();
System.out.println("总条数:" + totalElements);
System.out.println("总页数:" + totalPages);
items.forEach(item -> System.out.println(item));
}
使用template
public void testQuery1() {
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 结果过滤
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "title", "price"}, null));
// 添加查询条件
queryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米手机")); // title == "小米手机"
// 排序
queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC)); // 根据价格降序排序
// 分页
queryBuilder.withPageable(PageRequest.of(0, 2));
// 执行查询
SearchHits<Item> searchHits = template.search(queryBuilder.build(), Item.class);
// 总数
long totalHits = searchHits.getTotalHits();
// 结果
List<Item> itemList = searchHits.get()
.map(SearchHit::getContent)
.collect(Collectors.toList());
System.out.println(totalHits);
itemList.forEach(System.out::println);
}
NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体
5.6.3 聚合查询
5.6.3.1 聚合为桶
不推荐使用repository
public void testAgg(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 不查询任何结果
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
// 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
queryBuilder.addAggregation(
AggregationBuilders.terms("brands").field("brand"));
// 2、查询,需要把结果强转为AggregatedPage类型 方法已过时
AggregatedPage<Item> aggPage = (AggregatedPage<Item>) repository.search(queryBuilder.build());
// 3、解析
// 3.1、从结果中取出名为brands的那个聚合,
ParsedStringTerms agg = (ParsedStringTerms) aggPage.getAggregation("brands");
// 3.2、获取桶
List<? extends Terms.Bucket> buckets = agg.getBuckets();
// 3.3、遍历
for (Terms.Bucket bucket : buckets) {
System.out.println(bucket.getKeyAsString()+"==="+bucket.getDocCount());
}
}
推荐使用template
public void testAgg() {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 不查询任何结果
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));
// 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand"));
// 2 查询
SearchHits<Item> searchHits = template.search(queryBuilder.build(), Item.class);
// 3 解析
Aggregations aggregations = searchHits.getAggregations();
// 3.1 从结果中取出名为brands的那个聚合,
Terms agg = (Terms) aggregations.get("brands");
// 3.2 获取桶
for (Terms.Bucket bucket : agg.getBuckets()) {
String key = bucket.getKeyAsString();
long count = bucket.getDocCount();
System.out.println(key + "==" + count);
}
}
结果
5.6.3.2 嵌套聚合
根据品牌求价格平均值
public void testAgg() {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 不查询任何结果
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));
// 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
queryBuilder.addAggregation(
AggregationBuilders.terms("brands").field("brand")
.subAggregation(AggregationBuilders.avg("priceAvg").field("price"))
);
// 2 查询
SearchHits<Item> searchHits = template.search(queryBuilder.build(), Item.class);
// 3 解析
Aggregations aggregations = searchHits.getAggregations();
// 3.1 从结果中取出名为brands的那个聚合,
Terms agg = (Terms) aggregations.get("brands");
// 3.2 获取桶
for (Terms.Bucket bucket : agg.getBuckets()) {
String key = bucket.getKeyAsString();
long count = bucket.getDocCount();
// 获取嵌套查询结果
ParsedAvg priceAvg = bucket.getAggregations().get("priceAvg");
System.out.println(key + "==" + count + "==" + priceAvg.getValue());
}
}
根据品牌求价格总合
public void testAgg() {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 不查询任何结果
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{}, null));
// 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
queryBuilder.addAggregation(
AggregationBuilders.terms("brands").field("brand")
.subAggregation(AggregationBuilders.avg("priceAvg").field("price"))
.subAggregation(AggregationBuilders.sum("priceSum").field("price"))
);
// 2 查询
SearchHits<Item> searchHits = template.search(queryBuilder.build(), Item.class);
// 3 解析
Aggregations aggregations = searchHits.getAggregations();
// 3.1 从结果中取出名为brands的那个聚合,
Terms agg = (Terms) aggregations.get("brands");
// 3.2 获取桶
for (Terms.Bucket bucket : agg.getBuckets()) {
// 品牌名
String key = bucket.getKeyAsString();
// 数量
long count = bucket.getDocCount();
// 获取嵌套查询结果
// 价格平均值
ParsedAvg priceAvg = bucket.getAggregations().get("priceAvg");
// 价格总计
ParsedSum priceSum = bucket.getAggregations().get("priceSum");
System.out.println(key + "==" + count +"=="+priceSum.getValue()+"=="+ priceAvg.getValue());
}
}
结果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现