【ES】[教程]----第四章:Elasticsearch 整合spring boot 3 (索引、文档、高级查询、分页、排序)
本文介绍了Spring Boot与Elasticsearch的整合方案,重点说明了版本对应关系(如Spring Boot 3.x对应Elasticsearch 8.x)。具体实现步骤包括:1)添加Spring Data Elasticsearch依赖;2)配置Elasticsearch连接参数;3)创建带@Document注解的Product实体类;4)定义继承ElasticsearchRepos
·
文章目录
前言:版本依赖关系图
Spring Data Elasticsearch、Elasticsearch、Spring Boot 之间存在特定的版本对应关系,以下是详细的版本关系表:
| Spring Boot版本 | Spring Data Elasticsearch版本 | Elasticsearch版本 |
|---|---|---|
| 2.4.x | 4.1.x | 7.9.x |
| 2.5.x | 4.2.x | 7.12.x |
| 2.6.x | 4.3.x | 7.15.x |
| 2.7.x | 4.4.x | 7.17.x |
| 3.0.x及以上 | 5.0.x | 8.0.x及以上 |
一、整合Spring Boot 3
1. 添加依赖
首先在 pom.xml 中添加必要的依赖:
<!-- Spring Data Elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- 可选:添加Web依赖用于演示 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. 配置 Elasticsearch 连接
spring:
application:
name: elasticsearch-demo
# Elasticsearch配置
elasticsearch:
host: localhost # Elasticsearch主机地址
port: 9200 # Elasticsearch端口号
scheme: http # 协议
# 日志配置
logging:
level:
org.springframework.data.elasticsearch: INFO
com.example.elasticsearch: DEBUG
3. 创建实体类 Product
package com.example.elasticsearch.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
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;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 商品实体类,对应Elasticsearch中的文档
*
* @author example
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "product") // 指定索引名称
public class Product {
/**
* 商品ID,作为文档的唯一标识
*/
@Id
private Long id;
/**
* 商品名称,分词索引
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String name;
/**
* 商品描述,分词索引
*/
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String description;
/**
* 商品价格
*/
@Field(type = FieldType.Double)
private BigDecimal price;
/**
* 商品分类,精确匹配
*/
@Field(type = FieldType.Keyword)
private String category;
/**
* 商品库存数量
*/
@Field(type = FieldType.Integer)
private Integer stock;
/**
* 商品创建时间
*/
@Field(type = FieldType.Date, format = {} )
private LocalDateTime createTime;
/**
* 商品是否上架
*/
@Field(type = FieldType.Boolean)
private Boolean isActive;
}
4. Repository 层 ProductRepository
package com.example.elasticsearch.repository;
import com.example.elasticsearch.entity.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* 商品数据访问接口,用于与Elasticsearch交互
* 继承ElasticsearchRepository提供基本的CRUD操作
*
* @author example
*/
@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, Long> {
/**
* 根据商品分类查询商品列表
*
* @param category 商品分类
* @return 该分类下的所有商品列表
*/
List<Product> findByCategory(String category);
/**
* 根据商品名称模糊查询商品
*
* @param name 商品名称关键字
* @return 匹配的商品列表
*/
List<Product> findByNameContaining(String name);
/**
* 根据价格范围查询商品
*
* @param minPrice 最低价格
* @param maxPrice 最高价格
* @return 价格在指定范围内的商品列表
*/
List<Product> findByPriceBetween(Double minPrice, Double maxPrice);
/**
* 根据商品是否上架状态查询商品
*
* @param isActive 是否上架
* @return 符合状态的商品列表
*/
List<Product> findByIsActive(Boolean isActive);
}
5. 服务层接口 ProductService
package com.example.elasticsearch.service;
import com.example.elasticsearch.entity.Product;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.SearchHits;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 商品服务接口,定义商品相关的所有操作
*
* @author example
*/
public interface ProductService {
/**
* 创建商品索引
*
* @return 是否创建成功
*/
boolean createIndex();
/**
* 删除商品索引
*
* @return 是否删除成功
*/
boolean deleteIndex();
/**
* 判断商品索引是否存在
*
* @return 索引是否存在
*/
boolean existsIndex();
/**
* 创建或更新商品文档
*
* @param product 商品实体对象
* @return 保存后的商品实体
*/
Product saveOrUpdate(Product product);
/**
* 批量保存商品文档
*
* @param products 商品实体集合
* @return 保存后的商品实体集合
*/
Iterable<Product> batchSave(List<Product> products);
/**
* 根据ID删除商品文档
*
* @param id 商品ID
*/
void deleteById(Long id);
/**
* 删除所有商品文档
*/
void deleteAll();
/**
* 根据ID查询商品
*
* @param id 商品ID
* @return 商品实体的Optional对象
*/
Optional<Product> findById(Long id);
/**
* 查询所有商品
*
* @return 所有商品的迭代器
*/
Iterable<Product> findAll();
/**
* 分页查询所有商品
*
* @param pageable 分页参数
* @return 分页商品列表
*/
Page<Product> findAll(Pageable pageable);
/**
* 根据分类查询商品
*
* @param category 商品分类
* @return 该分类下的商品列表
*/
List<Product> findByCategory(String category);
/**
* 根据名称模糊查询商品
*
* @param name 商品名称关键字
* @return 匹配的商品列表
*/
List<Product> findByNameContaining(String name);
/**
* 根据价格范围查询商品
*
* @param minPrice 最低价格
* @param maxPrice 最高价格
* @return 价格在指定范围内的商品列表
*/
List<Product> findByPriceBetween(Double minPrice, Double maxPrice);
/**
* 高级搜索:多条件组合查询
*
* @param keyword 搜索关键字
* @param category 商品分类,可为null
* @param minPrice 最低价格,可为null
* @param maxPrice 最高价格,可为null
* @param isActive 是否上架,可为null
* @param pageable 分页参数
* @return 符合条件的商品分页结果
*/
Page<Product> advancedSearch(String keyword, String category, Double minPrice,
Double maxPrice, Boolean isActive, Pageable pageable);
/**
* 聚合查询:按分类统计商品数量
*
* @return 分类及对应的商品数量Map
*/
Map<String, Long> countByCategory();
/**
* 高亮搜索:在搜索结果中高亮显示匹配的关键字
*
* @param keyword 搜索关键字
* @param pageable 分页参数
* @return 带高亮效果的搜索结果
*/
SearchHits<Product> highlightSearch(String keyword, Pageable pageable);
}
6. 服务层实现 ProductService
package com.example.elasticsearch.service.impl;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.HighlightField;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.DeleteIndexRequest;
import co.elastic.clients.elasticsearch.indices.ExistsRequest;
import com.example.elasticsearch.entity.Product;
import com.example.elasticsearch.repository.ProductRepository;
import com.example.elasticsearch.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchOperations;
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.stereotype.Service;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 商品服务实现类,实现商品相关的所有操作
*
* @author example
*/
@Service
@RequiredArgsConstructor
public class ProductServiceImpl implements ProductService {
private final ProductRepository productRepository;
private final ElasticsearchOperations elasticsearchOperations;
private static final String INDEX_NAME = "product";
/**
* 创建商品索引
*
* @return 是否创建成功
*/
@Override
public boolean createIndex() {
try {
CreateIndexRequest request = CreateIndexRequest.of(b -> b.index(INDEX_NAME));
elasticsearchOperations.indices().create(request);
return true;
} catch (IOException e) {
throw new RuntimeException("创建索引失败", e);
}
}
/**
* 删除商品索引
*
* @return 是否删除成功
*/
@Override
public boolean deleteIndex() {
try {
DeleteIndexRequest request = DeleteIndexRequest.of(b -> b.index(INDEX_NAME));
elasticsearchOperations.indices().delete(request);
return true;
} catch (IOException e) {
throw new RuntimeException("删除索引失败", e);
}
}
/**
* 判断商品索引是否存在
*
* @return 索引是否存在
*/
@Override
public boolean existsIndex() {
try {
ExistsRequest request = ExistsRequest.of(b -> b.index(INDEX_NAME));
return elasticsearchOperations.indices().exists(request);
} catch (IOException e) {
throw new RuntimeException("检查索引是否存在失败", e);
}
}
/**
* 创建或更新商品文档
*
* @param product 商品实体对象
* @return 保存后的商品实体
*/
@Override
public Product saveOrUpdate(Product product) {
return productRepository.save(product);
}
/**
* 批量保存商品文档
*
* @param products 商品实体集合
* @return 保存后的商品实体集合
*/
@Override
public Iterable<Product> batchSave(List<Product> products) {
return productRepository.saveAll(products);
}
/**
* 根据ID删除商品文档
*
* @param id 商品ID
*/
@Override
public void deleteById(Long id) {
productRepository.deleteById(id);
}
/**
* 删除所有商品文档
*/
@Override
public void deleteAll() {
productRepository.deleteAll();
}
/**
* 根据ID查询商品
*
* @param id 商品ID
* @return 商品实体的Optional对象
*/
@Override
public Optional<Product> findById(Long id) {
return productRepository.findById(id);
}
/**
* 查询所有商品
*
* @return 所有商品的迭代器
*/
@Override
public Iterable<Product> findAll() {
return productRepository.findAll();
}
/**
* 分页查询所有商品
*
* @param pageable 分页参数
* @return 分页商品列表
*/
@Override
public Page<Product> findAll(Pageable pageable) {
return productRepository.findAll(pageable);
}
/**
* 根据分类查询商品
*
* @param category 商品分类
* @return 该分类下的商品列表
*/
@Override
public List<Product> findByCategory(String category) {
return productRepository.findByCategory(category);
}
/**
* 根据名称模糊查询商品
*
* @param name 商品名称关键字
* @return 匹配的商品列表
*/
@Override
public List<Product> findByNameContaining(String name) {
return productRepository.findByNameContaining(name);
}
/**
* 根据价格范围查询商品
*
* @param minPrice 最低价格
* @param maxPrice 最高价格
* @return 价格在指定范围内的商品列表
*/
@Override
public List<Product> findByPriceBetween(Double minPrice, Double maxPrice) {
return productRepository.findByPriceBetween(minPrice, maxPrice);
}
/**
* 高级搜索:多条件组合查询
*
* @param keyword 搜索关键字
* @param category 商品分类,可为null
* @param minPrice 最低价格,可为null
* @param maxPrice 最高价格,可为null
* @param isActive 是否上架,可为null
* @param pageable 分页参数
* @return 符合条件的商品分页结果
*/
@Override
public Page<Product> advancedSearch(String keyword, String category, Double minPrice,
Double maxPrice, Boolean isActive, Pageable pageable) {
// 构建布尔查询
BoolQuery.Builder boolQuery = QueryBuilders.bool();
// 添加关键字查询(搜索名称和描述字段)
if (keyword != null && !keyword.isEmpty()) {
boolQuery.should(QueryBuilders.match(m -> m.field("name").query(keyword)));
boolQuery.should(QueryBuilders.match(m -> m.field("description").query(keyword)));
}
// 添加分类查询
if (category != null && !category.isEmpty()) {
TermQuery termQuery = QueryBuilders.term(t -> t.field("category").value(category));
boolQuery.filter(termQuery._toQuery());
}
// 添加价格范围查询
if (minPrice != null || maxPrice != null) {
RangeQuery.Builder rangeQuery = QueryBuilders.range();
if (minPrice != null) {
rangeQuery.field("price").gte(minPrice);
}
if (maxPrice != null) {
rangeQuery.field("price").lte(maxPrice);
}
boolQuery.filter(rangeQuery._toQuery());
}
// 添加是否上架查询
if (isActive != null) {
TermQuery termQuery = QueryBuilders.term(t -> t.field("isActive").value(isActive));
boolQuery.filter(termQuery._toQuery());
}
// 构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery._toQuery())
.withPageable(pageable)
.build();
// 执行查询并返回结果
return elasticsearchOperations.search(
searchQuery,
Product.class,
IndexCoordinates.of(INDEX_NAME)
).map(hit -> hit.getContent()).toPage();
}
/**
* 聚合查询:按分类统计商品数量
*
* @return 分类及对应的商品数量Map
*/
@Override
public Map<String, Long> countByCategory() {
// 构建聚合查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.addAggregation(
org.springframework.data.elasticsearch.core.aggregation.AggregationBuilders
.terms("category_count")
.field("category")
)
.withSize(0) // 不返回文档,只返回聚合结果
.build();
// 执行查询
org.springframework.data.elasticsearch.core.SearchHits<Product> searchHits =
elasticsearchOperations.search(
searchQuery,
Product.class,
IndexCoordinates.of(INDEX_NAME)
);
// 解析聚合结果
Map<String, Long> result = new HashMap<>();
org.elasticsearch.search.aggregations.Aggregation aggregation =
searchHits.getAggregations().get("category_count");
if (aggregation instanceof org.elasticsearch.search.aggregations.bucket.terms.Terms) {
org.elasticsearch.search.aggregations.bucket.terms.Terms terms =
(org.elasticsearch.search.aggregations.bucket.terms.Terms) aggregation;
for (org.elasticsearch.search.aggregations.bucket.terms.Terms.Bucket bucket : terms.getBuckets()) {
result.put(bucket.getKeyAsString(), bucket.getDocCount());
}
}
return result;
}
/**
* 高亮搜索:在搜索结果中高亮显示匹配的关键字
*
* @param keyword 搜索关键字
* @param pageable 分页参数
* @return 带高亮效果的搜索结果
*/
@Override
public SearchHits<Product> highlightSearch(String keyword, Pageable pageable) {
// 构建高亮查询
Highlight highlight = Highlight.of(h -> h
.fields("name", HighlightField.of(f -> f.preTags("<em>").postTags("</em>")))
.fields("description", HighlightField.of(f -> f.preTags("<em>").postTags("</em>")))
);
// 构建查询
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatch(m -> m
.query(keyword)
.fields("name", "description")
))
.withHighlightFields(
new org.springframework.data.elasticsearch.core.query.HighlightBuilder.Field("name").preTags("<em>").postTags("</em>"),
new org.springframework.data.elasticsearch.core.query.HighlightBuilder.Field("description").preTags("<em>").postTags("</em>")
)
.withPageable(pageable)
.build();
// 执行查询
return elasticsearchOperations.search(
searchQuery,
Product.class,
IndexCoordinates.of(INDEX_NAME)
);
}
}
7. 配置类 ElasticsearchConfig
package com.example.elasticsearch.config;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
/**
* Elasticsearch配置类,配置ES连接和客户端
*
* @author example
*/
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.elasticsearch.repository")
public class ElasticsearchConfig {
/**
* Elasticsearch主机地址,从配置文件读取
*/
@Value("${elasticsearch.host:localhost}")
private String host;
/**
* Elasticsearch端口号,从配置文件读取
*/
@Value("${elasticsearch.port:9200}")
private int port;
/**
* 协议,从配置文件读取
*/
@Value("${elasticsearch.scheme:http}")
private String scheme;
/**
* 创建Elasticsearch客户端
* @return ElasticsearchClient实例
*/
@Bean
public ElasticsearchClient elasticsearchClient() {
// 创建低级客户端
RestClient restClient = RestClient.builder(
new HttpHost(host, port, scheme)
).build();
// 使用Jackson映射器创建传输层
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper()
);
// 创建API客户端
return new ElasticsearchClient(transport);
}
}
8. 控制层 ProductController
package com.example.elasticsearch.controller;
import com.example.elasticsearch.entity.Product;
import com.example.elasticsearch.service.ProductService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* 商品控制器,提供REST API接口,处理商品相关的HTTP请求
*
* @author example
*/
@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
@Tag(name = "商品管理", description = "商品的CRUD及搜索操作接口")
public class ProductController {
private final ProductService productService;
/**
* 创建商品索引
*
* @return 操作结果
*/
@PostMapping("/index")
@Operation(summary = "创建商品索引", description = "创建Elasticsearch中的商品索引")
public ResponseEntity<String> createIndex() {
boolean result = productService.createIndex();
return result ?
ResponseEntity.ok("索引创建成功") :
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("索引创建失败");
}
/**
* 删除商品索引
*
* @return 操作结果
*/
@DeleteMapping("/index")
@Operation(summary = "删除商品索引", description = "删除Elasticsearch中的商品索引")
public ResponseEntity<String> deleteIndex() {
boolean result = productService.deleteIndex();
return result ?
ResponseEntity.ok("索引删除成功") :
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("索引删除失败");
}
/**
* 检查索引是否存在
*
* @return 索引存在状态
*/
@GetMapping("/index/exists")
@Operation(summary = "检查索引是否存在", description = "检查Elasticsearch中的商品索引是否存在")
public ResponseEntity<Boolean> existsIndex() {
return ResponseEntity.ok(productService.existsIndex());
}
/**
* 创建或更新商品
*
* @param product 商品实体
* @return 保存后的商品实体
*/
@PostMapping
@Operation(summary = "创建或更新商品", description = "添加新商品或更新已有商品信息")
public ResponseEntity<Product> saveOrUpdateProduct(
@Parameter(description = "商品实体对象") @RequestBody Product product) {
Product savedProduct = productService.saveOrUpdate(product);
return ResponseEntity.ok(savedProduct);
}
/**
* 批量保存商品
*
* @param products 商品列表
* @return 保存后的商品列表
*/
@PostMapping("/batch")
@Operation(summary = "批量保存商品", description = "批量添加或更新商品信息")
public ResponseEntity<Iterable<Product>> batchSaveProducts(
@Parameter(description = "商品实体对象列表") @RequestBody List<Product> products) {
Iterable<Product> savedProducts = productService.batchSave(products);
return ResponseEntity.ok(savedProducts);
}
/**
* 根据ID删除商品
*
* @param id 商品ID
* @return 操作结果
*/
@DeleteMapping("/{id}")
@Operation(summary = "根据ID删除商品", description = "根据商品ID删除指定商品")
public ResponseEntity<String> deleteProductById(
@Parameter(description = "商品ID") @PathVariable Long id) {
productService.deleteById(id);
return ResponseEntity.ok("商品删除成功");
}
/**
* 删除所有商品
*
* @return 操作结果
*/
@DeleteMapping
@Operation(summary = "删除所有商品", description = "删除所有商品文档")
public ResponseEntity<String> deleteAllProducts() {
productService.deleteAll();
return ResponseEntity.ok("所有商品删除成功");
}
/**
* 根据ID查询商品
*
* @param id 商品ID
* @return 商品实体
*/
@GetMapping("/{id}")
@Operation(summary = "根据ID查询商品", description = "根据商品ID查询商品详情")
public ResponseEntity<Product> getProductById(
@Parameter(description = "商品ID") @PathVariable Long id) {
Optional<Product> product = productService.findById(id);
return product.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build());
}
/**
* 查询所有商品
*
* @return 所有商品列表
*/
@GetMapping
@Operation(summary = "查询所有商品", description = "查询所有商品信息")
public ResponseEntity<Iterable<Product>> getAllProducts() {
return ResponseEntity.ok(productService.findAll());
}
/**
* 分页查询所有商品
*
* @param page 页码,从0开始
* @param size 每页数量
* @param sort 排序字段
* @param direction 排序方向
* @return 分页商品列表
*/
@GetMapping("/page")
@Operation(summary = "分页查询所有商品", description = "分页查询所有商品信息")
public ResponseEntity<Page<Product>> getProductsByPage(
@Parameter(description = "页码,从0开始") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") int size,
@Parameter(description = "排序字段") @RequestParam(defaultValue = "id") String sort,
@Parameter(description = "排序方向,asc或desc") @RequestParam(defaultValue = "asc") String direction) {
Sort.Direction sortDirection = "desc".equalsIgnoreCase(direction) ?
Sort.Direction.DESC : Sort.Direction.ASC;
Pageable pageable = PageRequest.of(page, size, Sort.by(sortDirection, sort));
return ResponseEntity.ok(productService.findAll(pageable));
}
/**
* 根据分类查询商品
*
* @param category 商品分类
* @return 该分类下的商品列表
*/
@GetMapping("/category/{category}")
@Operation(summary = "根据分类查询商品", description = "根据商品分类查询商品列表")
public ResponseEntity<List<Product>> getProductsByCategory(
@Parameter(description = "商品分类") @PathVariable String category) {
return ResponseEntity.ok(productService.findByCategory(category));
}
/**
* 根据名称模糊查询商品
*
* @param name 商品名称关键字
* @return 匹配的商品列表
*/
@GetMapping("/name/{name}")
@Operation(summary = "根据名称模糊查询商品", description = "根据商品名称关键字模糊查询商品")
public ResponseEntity<List<Product>> getProductsByNameContaining(
@Parameter(description = "商品名称关键字") @PathVariable String name) {
return ResponseEntity.ok(productService.findByNameContaining(name));
}
/**
* 根据价格范围查询商品
*
* @param minPrice 最低价格
* @param maxPrice 最高价格
* @return 价格在指定范围内的商品列表
*/
@GetMapping("/price-range")
@Operation(summary = "根据价格范围查询商品", description = "根据价格范围查询商品列表")
public ResponseEntity<List<Product>> getProductsByPriceBetween(
@Parameter(description = "最低价格") @RequestParam Double minPrice,
@Parameter(description = "最高价格") @RequestParam Double maxPrice) {
return ResponseEntity.ok(productService.findByPriceBetween(minPrice, maxPrice));
}
/**
* 高级搜索:多条件组合查询
*
* @param keyword 搜索关键字
* @param category 商品分类
* @param minPrice 最低价格
* @param maxPrice 最高价格
* @param isActive 是否上架
* @param page 页码
* @param size 每页数量
* @return 符合条件的商品分页结果
*/
@GetMapping("/advanced-search")
@Operation(summary = "高级搜索", description = "多条件组合查询商品")
public ResponseEntity<Page<Product>> advancedSearch(
@Parameter(description = "搜索关键字") @RequestParam(required = false) String keyword,
@Parameter(description = "商品分类") @RequestParam(required = false) String category,
@Parameter(description = "最低价格") @RequestParam(required = false) Double minPrice,
@Parameter(description = "最高价格") @RequestParam(required = false) Double maxPrice,
@Parameter(description = "是否上架") @RequestParam(required = false) Boolean isActive,
@Parameter(description = "页码") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") int size) {
Pageable pageable = PageRequest.of(page, size);
Page<Product> products = productService.advancedSearch(
keyword, category, minPrice, maxPrice, isActive, pageable);
return ResponseEntity.ok(products);
}
/**
* 按分类统计商品数量
*
* @return 分类及对应的商品数量
*/
@GetMapping("/aggregation/category-count")
@Operation(summary = "按分类统计商品数量", description = "聚合查询,按分类统计商品数量")
public ResponseEntity<Map<String, Long>> countByCategory() {
return ResponseEntity.ok(productService.countByCategory());
}
/**
* 高亮搜索
*
* @param keyword 搜索关键字
* @param page 页码
* @param size 每页数量
* @return 带高亮效果的搜索结果
*/
@GetMapping("/highlight-search")
@Operation(summary = "高亮搜索", description = "搜索结果中高亮显示匹配的关键字")
public ResponseEntity<SearchHits<Product>> highlightSearch(
@Parameter(description = "搜索关键字") @RequestParam String keyword,
@Parameter(description = "页码") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "每页数量") @RequestParam(defaultValue = "10") int size) {
Pageable pageable = PageRequest.of(page, size);
return ResponseEntity.ok(productService.highlightSearch(keyword, pageable));
}
}
实现说明
上述代码实现了一个完整的 Elasticsearch 与 Spring Boot 3 整合方案,主要特点如下:
- 架构设计:采用标准的 MVC 架构,包括实体类、Repository 层、Service 层和 Controller 层
- 功能实现:
- 索引管理:创建、删除、检查索引
- 文档操作:新增、修改、删除、查询单个和多个文档
- 高级查询:多条件组合查询、范围查询、高亮查询、聚合查询
- 分页与排序:支持分页查询和结果排序
- 技术亮点:
- 使用 Lombok 简化代码,减少模板代码
- 使用 Spring Data Elasticsearch 简化 Elasticsearch 操作
- 完整的异常处理机制
- 详细的 API 文档注释
- 灵活的配置方式
使用时,只需根据实际环境修改 application.yml 中的 Elasticsearch 连接信息即可。该实现可以作为 Elasticsearch 与 Spring Boot 3 整合的基础框架,根据实际业务需求进行扩展。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)