本文目录导读:

我来详细介绍SpringBoot集成Elasticsearch实现搜索功能的完整方案。
环境配置
添加依赖
<!-- SpringBoot 2.x 使用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- SpringBoot 3.x 使用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
</dependency>
配置文件
spring:
elasticsearch:
uris: http://localhost:9200
connection-timeout: 10s
socket-timeout: 30s
username: elastic # 如果有认证
password: changeme
# 或者使用RestHighLevelClient配置
data:
elasticsearch:
cluster-name: docker-cluster
cluster-nodes: localhost:9300
基础配置类
@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.repository")
public class ElasticsearchConfig {
@Bean
public ElasticsearchClient elasticsearchClient(RestClient restClient) {
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
@Bean
public RestClient restClient() {
RestClientBuilder builder = RestClient.builder(
new HttpHost("localhost", 9200, "http")
);
return builder.build();
}
}
文档实体类
@Data
@Document(indexName = "products", createIndex = true)
@Setting(shards = 3, replicas = 1)
public class Product {
@Id
private String id;
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String name;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String description;
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Integer)
private Integer stock;
@Field(type = FieldType.Date, format = DateFormat.date_time)
private LocalDateTime createTime;
@Field(type = FieldType.Boolean)
private Boolean status;
}
Repository层
@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
// 基础查询
List<Product> findByName(String name);
List<Product> findByNameAndPrice(String name, Double price);
// 模糊查询
List<Product> findByNameContaining(String name);
// 范围查询
List<Product> findByPriceBetween(Double min, Double max);
// 多条件查询
List<Product> findByCategoryAndPriceLessThan(String category, Double price);
// 排序查询
List<Product> findByNameContainingOrderByPriceDesc(String name);
}
搜索服务类
@Service
@Slf4j
public class SearchService {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Autowired
private ProductRepository productRepository;
@Autowired
private ElasticsearchClient elasticsearchClient;
/**
* 基础搜索
*/
public List<Product> searchByKeyword(String keyword) {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("name", keyword))
.withPageable(PageRequest.of(0, 10))
.build();
SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
return searchHits.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
/**
* 高亮搜索
*/
public List<Map<String, Object>> searchWithHighlight(String keyword) {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("name", keyword))
.withHighlightFields(
new HighlightBuilder.Field("name")
.preTags("<span style='color:red'>")
.postTags("</span>")
)
.withPageable(PageRequest.of(0, 10))
.build();
SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
return searchHits.stream().map(hit -> {
Map<String, Object> result = new HashMap<>();
result.put("product", hit.getContent());
result.put("highlight", hit.getHighlightFields());
result.put("score", hit.getScore());
return result;
}).collect(Collectors.toList());
}
/**
* 布尔查询
*/
public List<Product> searchWithFilters(String keyword, String category, Double priceMin, Double priceMax) {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 关键词搜索
if (StringUtils.hasText(keyword)) {
boolQuery.must(QueryBuilders.matchQuery("name", keyword));
}
// 分类过滤
if (StringUtils.hasText(category)) {
boolQuery.filter(QueryBuilders.termQuery("category", category));
}
// 价格范围
if (priceMin != null || priceMax != null) {
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
if (priceMin != null) rangeQuery.gte(priceMin);
if (priceMax != null) rangeQuery.lte(priceMax);
boolQuery.filter(rangeQuery);
}
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.withPageable(PageRequest.of(0, 10))
.withSort(Sort.by(Sort.Direction.DESC, "price"))
.build();
SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
return searchHits.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
}
/**
* 聚合查询
*/
public Map<String, Object> aggregateSearch(String category) {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(StringUtils.hasText(category) ?
QueryBuilders.termQuery("category", category) : null)
.addAggregation(AggregationBuilders.terms("category_agg").field("category"))
.addAggregation(AggregationBuilders.stats("price_stats").field("price"))
.build();
SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
Map<String, Object> result = new HashMap<>();
result.put("products", searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList()));
// 解析聚合结果
Aggregations aggregations = searchHits.getAggregations();
if (aggregations != null) {
ParsedStringTerms categoryAgg = aggregations.get("category_agg");
ParsedStats priceStats = aggregations.get("price_stats");
result.put("categories", categoryAgg.getBuckets().stream()
.map(bucket -> Map.of("key", bucket.getKey(), "count", bucket.getDocCount()))
.collect(Collectors.toList()));
result.put("priceStats", Map.of(
"min", priceStats.getMin(),
"max", priceStats.getMax(),
"avg", priceStats.getAvg(),
"sum", priceStats.getSum()
));
}
return result;
}
}
Controller层
@RestController
@RequestMapping("/api/search")
@Slf4j
public class SearchController {
@Autowired
private SearchService searchService;
@Autowired
private ProductRepository productRepository;
/**
* 基础搜索
*/
@GetMapping("/basic")
public Result<List<Product>> basicSearch(@RequestParam String keyword) {
return Result.success(searchService.searchByKeyword(keyword));
}
/**
* 高亮搜索
*/
@GetMapping("/highlight")
public Result<List<Map<String, Object>>> highlightSearch(@RequestParam String keyword) {
return Result.success(searchService.searchWithHighlight(keyword));
}
/**
* 高级搜索
*/
@GetMapping("/advanced")
public Result<List<Product>> advancedSearch(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) String category,
@RequestParam(required = false) Double priceMin,
@RequestParam(required = false) Double priceMax) {
return Result.success(searchService.searchWithFilters(keyword, category, priceMin, priceMax));
}
/**
* 聚合查询
*/
@GetMapping("/aggregate")
public Result<Map<String, Object>> aggregateSearch(
@RequestParam(required = false) String category) {
return Result.success(searchService.aggregateSearch(category));
}
/**
* 添加文档
*/
@PostMapping("/index")
public Result<Product> indexDocument(@RequestBody Product product) {
product.setCreateTime(LocalDateTime.now());
Product saved = productRepository.save(product);
return Result.success(saved);
}
/**
* 批量导入
*/
@PostMapping("/batch-index")
public Result<String> batchIndex(@RequestBody List<Product> products) {
products.forEach(p -> p.setCreateTime(LocalDateTime.now()));
productRepository.saveAll(products);
return Result.success("批量导入成功,共" + products.size() + "条");
}
/**
* 删除文档
*/
@DeleteMapping("/{id}")
public Result<String> deleteDocument(@PathVariable String id) {
productRepository.deleteById(id);
return Result.success("删除成功");
}
}
ES初始化工具类
@Component
@Slf4j
public class ElasticsearchInitializer implements CommandLineRunner {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Override
public void run(String... args) throws Exception {
// 检查索引是否存在,不存在则创建
boolean indexExists = elasticsearchRestTemplate.indexOps(IndexCoordinates.of("products")).exists();
if (!indexExists) {
// 创建索引
elasticsearchRestTemplate.indexOps(Product.class).create();
// 设置IK分词器
String settings = "{\n" +
" \"analysis\": {\n" +
" \"analyzer\": {\n" +
" \"ik_smart_analyzer\": {\n" +
" \"type\": \"custom\",\n" +
" \"tokenizer\": \"ik_smart\"\n" +
" },\n" +
" \"ik_max_word_analyzer\": {\n" +
" \"type\": \"custom\",\n" +
" \"tokenizer\": \"ik_max_word\"\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
log.info("Elasticsearch索引初始化完成");
}
}
}
分页查询优化
@Service
public class PagedSearchService {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 带分页的搜索
*/
public Page<Product> searchWithPage(String keyword, int page, int size) {
Pageable pageable = PageRequest.of(page, size);
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("name", keyword))
.withPageable(pageable)
.build();
SearchHits<Product> searchHits = elasticsearchRestTemplate.search(searchQuery, Product.class);
List<Product> products = searchHits.stream()
.map(SearchHit::getContent)
.collect(Collectors.toList());
return new PageImpl<>(products, pageable, searchHits.getTotalHits());
}
/**
* 游标查询(深度分页)
*/
public List<Product> searchWithScroll(String keyword, int size) {
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.matchQuery("name", keyword))
.withPageable(PageRequest.of(0, size))
.build();
// 设置游标超时时间
long scrollTimeInMillis = 60000;
ScrolledPage<Product> scroll = elasticsearchRestTemplate.searchScrollStart(
scrollTimeInMillis, searchQuery, Product.class);
String scrollId = scroll.getScrollId();
List<Product> allProducts = new ArrayList<>();
while (scroll.hasContent()) {
allProducts.addAll(scroll.getContent());
scroll = elasticsearchRestTemplate.searchScrollContinue(scrollId, scrollTimeInMillis, Product.class);
}
elasticsearchRestTemplate.searchScrollClear(scrollId);
return allProducts;
}
}
完整搜索功能测试
@SpringBootTest
@Slf4j
public class SearchServiceTest {
@Autowired
private SearchService searchService;
@Autowired
private ProductRepository productRepository;
@BeforeEach
public void setup() {
// 准备测试数据
List<Product> products = Arrays.asList(
new Product(null, "iPhone 15", "Apple最新款智能手机", "电子产品", 6999.0, 100, LocalDateTime.now(), true),
new Product(null, "华为Mate 60", "华为旗舰手机", "电子产品", 5999.0, 200, LocalDateTime.now(), true),
new Product(null, "MacBook Pro", "Apple笔记本电脑", "电子产品", 14999.0, 50, LocalDateTime.now(), true)
);
productRepository.saveAll(products);
}
@Test
public void testSearch() {
List<Product> results = searchService.searchByKeyword("手机");
assertThat(results).isNotEmpty();
log.info("搜索结果:{}", results);
}
@Test
public void testHighlightSearch() {
List<Map<String, Object>> results = searchService.searchWithHighlight("iPhone");
assertThat(results).isNotEmpty();
results.forEach(r -> log.info("高亮结果:{}", r));
}
@Test
public void testFilterSearch() {
List<Product> results = searchService.searchWithFilters("手机", "电子产品", 5000.0, 8000.0);
assertThat(results).isNotEmpty();
results.forEach(p -> log.info("过滤结果:{}", p));
}
}
性能优化建议
-
索引优化
- 合理设置分片数和副本数
- 使用合适的字段类型
- 禁用不需要的索引
-
查询优化
// 使用filter代替query(不计算相关性得分) BoolQueryBuilder boolQuery = QueryBuilders.boolQuery() .filter(QueryBuilders.termQuery("category", "电子产品")); // 使用keyword字段进行精确查询 QueryBuilders.termQuery("category.keyword", "电子产品"); -
批量操作
// 批量导入时使用bulk BulkRequest bulkRequest = new BulkRequest(); products.forEach(product -> { bulkRequest.add(new IndexRequest("products") .id(product.getId()) .source(product, XContentType.JSON)); });
这套方案提供了完整的Elasticsearch搜索实现,涵盖了基础搜索、高亮显示、过滤查询、聚合分析等常见功能。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播