当前位置: 首页 > news >正文

SpringBoot3.x入门到精通系列:3.3 整合 Elasticsearch 详解

🎯 Elasticsearch简介

Elasticsearch是一个基于Lucene的分布式搜索和分析引擎,能够快速地存储、搜索和分析大量数据。它提供了RESTful API,支持多种查询方式,广泛应用于全文搜索、日志分析、实时数据分析等场景。

核心概念

  • Index: 索引,类似于数据库中的数据库
  • Type: 类型,类似于数据库中的表(ES 7.x后已废弃)
  • Document: 文档,类似于数据库中的行记录
  • Field: 字段,类似于数据库中的列
  • Mapping: 映射,定义文档字段的数据类型和索引方式
  • Shard: 分片,索引的物理分割单元
  • Replica: 副本,分片的备份

🚀 快速开始

1. 添加依赖

<dependencies><!-- SpringBoot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- SpringBoot Elasticsearch Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><!-- JSON处理 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><!-- 测试依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
</dependencies>

2. Elasticsearch配置

spring:# Elasticsearch配置elasticsearch:# Elasticsearch服务器地址uris: http://localhost:9200# 用户名(如果启用了安全认证)username: elastic# 密码(如果启用了安全认证)password: password# 连接超时时间connection-timeout: 10s# 读取超时时间socket-timeout: 30s# 数据源配置(用于对比)datasource:url: jdbc:mysql://localhost:3306/demo_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver# 日志配置
logging:level:org.springframework.data.elasticsearch: DEBUGorg.elasticsearch: DEBUG

🔧 Elasticsearch配置类

package com.example.demo.config;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;@Configuration
@EnableElasticsearchRepositories(basePackages = "com.example.demo.repository")
public class ElasticsearchConfig extends ElasticsearchConfiguration {@Value("${spring.elasticsearch.uris}")private String elasticsearchUrl;@Value("${spring.elasticsearch.username:}")private String username;@Value("${spring.elasticsearch.password:}")private String password;@Overridepublic ClientConfiguration clientConfiguration() {ClientConfiguration.Builder builder = ClientConfiguration.builder().connectedTo(elasticsearchUrl.replace("http://", "")).withConnectTimeout(10000).withSocketTimeout(30000);// 如果配置了用户名和密码if (!username.isEmpty() && !password.isEmpty()) {builder.withBasicAuth(username, password);}return builder.build();}
}

📊 文档实体类

1. 产品文档

package com.example.demo.document;import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;@Document(indexName = "products")
@Setting(numberOfShards = 1, numberOfReplicas = 0)
public class ProductDocument {@Idprivate String 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.Keyword)private String category;@Field(type = FieldType.Keyword)private String brand;@Field(type = FieldType.Double)private BigDecimal price;@Field(type = FieldType.Integer)private Integer stock;@Field(type = FieldType.Keyword)private List<String> tags;@Field(type = FieldType.Boolean)private Boolean available;@Field(type = FieldType.Double)private Double rating;@Field(type = FieldType.Integer)private Integer salesCount;@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime updateTime;// 构造函数public ProductDocument() {}public ProductDocument(String name, String description, String category, String brand, BigDecimal price) {this.name = name;this.description = description;this.category = category;this.brand = brand;this.price = price;this.available = true;this.rating = 0.0;this.salesCount = 0;this.createTime = LocalDateTime.now();this.updateTime = LocalDateTime.now();}// Getter和Setter方法public String getId() { return id; }public void setId(String id) { this.id = id; }public String getName() { return name; }public void setName(String name) { this.name = name; }public String getDescription() { return description; }public void setDescription(String description) { this.description = description; }public String getCategory() { return category; }public void setCategory(String category) { this.category = category; }public String getBrand() { return brand; }public void setBrand(String brand) { this.brand = brand; }public BigDecimal getPrice() { return price; }public void setPrice(BigDecimal price) { this.price = price; }public Integer getStock() { return stock; }public void setStock(Integer stock) { this.stock = stock; }public List<String> getTags() { return tags; }public void setTags(List<String> tags) { this.tags = tags; }public Boolean getAvailable() { return available; }public void setAvailable(Boolean available) { this.available = available; }public Double getRating() { return rating; }public void setRating(Double rating) { this.rating = rating; }public Integer getSalesCount() { return salesCount; }public void setSalesCount(Integer salesCount) { this.salesCount = salesCount; }public LocalDateTime getCreateTime() { return createTime; }public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }public LocalDateTime getUpdateTime() { return updateTime; }public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }@Overridepublic String toString() {return "ProductDocument{" +"id='" + id + '\'' +", name='" + name + '\'' +", description='" + description + '\'' +", category='" + category + '\'' +", brand='" + brand + '\'' +", price=" + price +", stock=" + stock +", available=" + available +", rating=" + rating +", salesCount=" + salesCount +'}';}
}

2. 文章文档

package com.example.demo.document;import com.fasterxml.jackson.annotation.JsonFormat;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.*;import java.time.LocalDateTime;
import java.util.List;@Document(indexName = "articles")
@Setting(numberOfShards = 1, numberOfReplicas = 0)
public class ArticleDocument {@Idprivate String id;@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")private String title;@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")private String content;@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")private String summary;@Field(type = FieldType.Keyword)private String author;@Field(type = FieldType.Keyword)private String category;@Field(type = FieldType.Keyword)private List<String> tags;@Field(type = FieldType.Integer)private Integer viewCount;@Field(type = FieldType.Integer)private Integer likeCount;@Field(type = FieldType.Boolean)private Boolean published;@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;@Field(type = FieldType.Date, format = DateFormat.date_hour_minute_second)@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime updateTime;// 构造函数public ArticleDocument() {}public ArticleDocument(String title, String content, String author, String category) {this.title = title;this.content = content;this.author = author;this.category = category;this.published = false;this.viewCount = 0;this.likeCount = 0;this.createTime = LocalDateTime.now();this.updateTime = LocalDateTime.now();}// Getter和Setter方法public String getId() { return id; }public void setId(String id) { this.id = id; }public String getTitle() { return title; }public void setTitle(String title) { this.title = title; }public String getContent() { return content; }public void setContent(String content) { this.content = content; }public String getSummary() { return summary; }public void setSummary(String summary) { this.summary = summary; }public String getAuthor() { return author; }public void setAuthor(String author) { this.author = author; }public String getCategory() { return category; }public void setCategory(String category) { this.category = category; }public List<String> getTags() { return tags; }public void setTags(List<String> tags) { this.tags = tags; }public Integer getViewCount() { return viewCount; }public void setViewCount(Integer viewCount) { this.viewCount = viewCount; }public Integer getLikeCount() { return likeCount; }public void setLikeCount(Integer likeCount) { this.likeCount = likeCount; }public Boolean getPublished() { return published; }public void setPublished(Boolean published) { this.published = published; }public LocalDateTime getCreateTime() { return createTime; }public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }public LocalDateTime getUpdateTime() { return updateTime; }public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
}

🔍 Repository接口

1. 产品Repository

package com.example.demo.repository;import com.example.demo.document.ProductDocument;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;import java.math.BigDecimal;
import java.util.List;@Repository
public interface ProductRepository extends ElasticsearchRepository<ProductDocument, String> {// 根据名称查找产品List<ProductDocument> findByName(String name);// 根据名称模糊查找产品List<ProductDocument> findByNameContaining(String name);// 根据分类查找产品List<ProductDocument> findByCategory(String category);// 根据品牌查找产品List<ProductDocument> findByBrand(String brand);// 根据价格范围查找产品List<ProductDocument> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice);// 根据可用性查找产品List<ProductDocument> findByAvailable(Boolean available);// 根据评分范围查找产品List<ProductDocument> findByRatingGreaterThanEqual(Double rating);// 复合查询:根据分类和价格范围查找产品List<ProductDocument> findByCategoryAndPriceBetween(String category, BigDecimal minPrice, BigDecimal maxPrice);// 分页查询:根据分类查找产品Page<ProductDocument> findByCategory(String category, Pageable pageable);// 自定义查询:多字段搜索@Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"name^2\", \"description\", \"category\", \"brand\"]}}")List<ProductDocument> searchByMultiFields(String keyword);// 自定义查询:根据标签搜索@Query("{\"terms\": {\"tags\": [\"?0\"]}}")List<ProductDocument> findByTag(String tag);// 自定义查询:热门产品(根据销量和评分)@Query("{\"bool\": {\"must\": [{\"range\": {\"salesCount\": {\"gte\": ?0}}}, {\"range\": {\"rating\": {\"gte\": ?1}}}]}}")List<ProductDocument> findPopularProducts(Integer minSales, Double minRating);
}

2. 文章Repository

package com.example.demo.repository;import com.example.demo.document.ArticleDocument;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
public interface ArticleRepository extends ElasticsearchRepository<ArticleDocument, String> {// 根据标题查找文章List<ArticleDocument> findByTitleContaining(String title);// 根据作者查找文章List<ArticleDocument> findByAuthor(String author);// 根据分类查找文章List<ArticleDocument> findByCategory(String category);// 根据发布状态查找文章List<ArticleDocument> findByPublished(Boolean published);// 根据作者和发布状态查找文章List<ArticleDocument> findByAuthorAndPublished(String author, Boolean published);// 分页查询:根据分类查找已发布文章Page<ArticleDocument> findByCategoryAndPublished(String category, Boolean published, Pageable pageable);// 自定义查询:全文搜索@Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"title^3\", \"content\", \"summary^2\"]}}")List<ArticleDocument> searchByContent(String keyword);// 自定义查询:根据标签搜索@Query("{\"terms\": {\"tags\": [\"?0\"]}}")List<ArticleDocument> findByTag(String tag);// 自定义查询:热门文章@Query("{\"bool\": {\"must\": [{\"term\": {\"published\": true}}, {\"range\": {\"viewCount\": {\"gte\": ?0}}}], \"should\": [{\"range\": {\"likeCount\": {\"gte\": ?1}}}]}}")List<ArticleDocument> findPopularArticles(Integer minViews, Integer minLikes);
}

🏗️ Service层实现

1. 产品搜索服务

package com.example.demo.service;import com.example.demo.document.ProductDocument;
import com.example.demo.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
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.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;import static org.elasticsearch.index.query.QueryBuilders.*;@Service
public class ProductSearchService {@Autowiredprivate ProductRepository productRepository;@Autowiredprivate ElasticsearchOperations elasticsearchOperations;/*** 保存产品*/public ProductDocument save(ProductDocument product) {return productRepository.save(product);}/*** 根据ID查找产品*/public Optional<ProductDocument> findById(String id) {return productRepository.findById(id);}/*** 查找所有产品*/public Iterable<ProductDocument> findAll() {return productRepository.findAll();}/*** 删除产品*/public void deleteById(String id) {productRepository.deleteById(id);}/*** 根据名称搜索产品*/public List<ProductDocument> searchByName(String name) {return productRepository.findByNameContaining(name);}/*** 根据分类搜索产品*/public List<ProductDocument> searchByCategory(String category) {return productRepository.findByCategory(category);}/*** 根据价格范围搜索产品*/public List<ProductDocument> searchByPriceRange(BigDecimal minPrice, BigDecimal maxPrice) {return productRepository.findByPriceBetween(minPrice, maxPrice);}/*** 多字段搜索*/public List<ProductDocument> multiFieldSearch(String keyword) {return productRepository.searchByMultiFields(keyword);}/*** 分页搜索产品*/public Page<ProductDocument> searchByCategory(String category, int page, int size) {Pageable pageable = PageRequest.of(page, size, Sort.by("createTime").descending());return productRepository.findByCategory(category, pageable);}/*** 复杂搜索:使用ElasticsearchOperations*/public List<ProductDocument> complexSearch(String keyword, String category, BigDecimal minPrice, BigDecimal maxPrice) {NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();// 构建复合查询var boolQuery = boolQuery();// 关键词搜索if (keyword != null && !keyword.isEmpty()) {boolQuery.must(multiMatchQuery(keyword, "name", "description", "brand"));}// 分类过滤if (category != null && !category.isEmpty()) {boolQuery.filter(termQuery("category", category));}// 价格范围过滤if (minPrice != null && maxPrice != null) {boolQuery.filter(rangeQuery("price").gte(minPrice).lte(maxPrice));}// 只搜索可用产品boolQuery.filter(termQuery("available", true));NativeSearchQuery searchQuery = queryBuilder.withQuery(boolQuery).withSort(Sort.by("salesCount").descending()).withSort(Sort.by("rating").descending()).build();SearchHits<ProductDocument> searchHits = elasticsearchOperations.search(searchQuery, ProductDocument.class);return searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList());}/*** 搜索热门产品*/public List<ProductDocument> searchPopularProducts(Integer minSales, Double minRating) {return productRepository.findPopularProducts(minSales, minRating);}/*** 根据标签搜索产品*/public List<ProductDocument> searchByTag(String tag) {return productRepository.findByTag(tag);}/*** 聚合搜索:按分类统计产品数量*/public void aggregateByCategory() {// 这里可以实现聚合查询的逻辑// 由于篇幅限制,这里只是一个示例方法}
}

🎮 Controller层

package com.example.demo.controller;import com.example.demo.document.ProductDocument;
import com.example.demo.service.ProductSearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;@RestController
@RequestMapping("/api/search")
@CrossOrigin(origins = "*")
public class SearchController {@Autowiredprivate ProductSearchService productSearchService;/*** 创建产品*/@PostMapping("/products")public ResponseEntity<ProductDocument> createProduct(@RequestBody ProductDocument product) {ProductDocument savedProduct = productSearchService.save(product);return ResponseEntity.ok(savedProduct);}/*** 根据ID获取产品*/@GetMapping("/products/{id}")public ResponseEntity<ProductDocument> getProduct(@PathVariable String id) {Optional<ProductDocument> product = productSearchService.findById(id);return product.map(ResponseEntity::ok).orElse(ResponseEntity.notFound().build());}/*** 获取所有产品*/@GetMapping("/products")public ResponseEntity<Iterable<ProductDocument>> getAllProducts() {Iterable<ProductDocument> products = productSearchService.findAll();return ResponseEntity.ok(products);}/*** 根据名称搜索产品*/@GetMapping("/products/search/name")public ResponseEntity<List<ProductDocument>> searchByName(@RequestParam String name) {List<ProductDocument> products = productSearchService.searchByName(name);return ResponseEntity.ok(products);}/*** 根据分类搜索产品*/@GetMapping("/products/search/category")public ResponseEntity<List<ProductDocument>> searchByCategory(@RequestParam String category) {List<ProductDocument> products = productSearchService.searchByCategory(category);return ResponseEntity.ok(products);}/*** 根据价格范围搜索产品*/@GetMapping("/products/search/price")public ResponseEntity<List<ProductDocument>> searchByPriceRange(@RequestParam BigDecimal minPrice,@RequestParam BigDecimal maxPrice) {List<ProductDocument> products = productSearchService.searchByPriceRange(minPrice, maxPrice);return ResponseEntity.ok(products);}/*** 多字段搜索*/@GetMapping("/products/search/multi")public ResponseEntity<List<ProductDocument>> multiFieldSearch(@RequestParam String keyword) {List<ProductDocument> products = productSearchService.multiFieldSearch(keyword);return ResponseEntity.ok(products);}/*** 分页搜索*/@GetMapping("/products/search/page")public ResponseEntity<Page<ProductDocument>> searchByPage(@RequestParam String category,@RequestParam(defaultValue = "0") int page,@RequestParam(defaultValue = "10") int size) {Page<ProductDocument> products = productSearchService.searchByCategory(category, page, size);return ResponseEntity.ok(products);}/*** 复杂搜索*/@GetMapping("/products/search/complex")public ResponseEntity<List<ProductDocument>> complexSearch(@RequestParam(required = false) String keyword,@RequestParam(required = false) String category,@RequestParam(required = false) BigDecimal minPrice,@RequestParam(required = false) BigDecimal maxPrice) {List<ProductDocument> products = productSearchService.complexSearch(keyword, category, minPrice, maxPrice);return ResponseEntity.ok(products);}/*** 搜索热门产品*/@GetMapping("/products/search/popular")public ResponseEntity<List<ProductDocument>> searchPopularProducts(@RequestParam(defaultValue = "100") Integer minSales,@RequestParam(defaultValue = "4.0") Double minRating) {List<ProductDocument> products = productSearchService.searchPopularProducts(minSales, minRating);return ResponseEntity.ok(products);}/*** 根据标签搜索产品*/@GetMapping("/products/search/tag")public ResponseEntity<List<ProductDocument>> searchByTag(@RequestParam String tag) {List<ProductDocument> products = productSearchService.searchByTag(tag);return ResponseEntity.ok(products);}/*** 删除产品*/@DeleteMapping("/products/{id}")public ResponseEntity<Map<String, String>> deleteProduct(@PathVariable String id) {productSearchService.deleteById(id);Map<String, String> response = new HashMap<>();response.put("status", "success");response.put("message", "产品删除成功");return ResponseEntity.ok(response);}
}

📊 最佳实践

1. 索引设计

  • 合理设置分片和副本数量
  • 选择合适的字段类型
  • 使用合适的分析器
  • 定期优化索引

2. 查询优化

  • 使用过滤器而非查询进行精确匹配
  • 合理使用聚合查询
  • 避免深度分页
  • 使用缓存提高性能

3. 数据同步

  • 实现数据库与ES的同步机制
  • 处理数据一致性问题
  • 监控同步状态
  • 实现故障恢复

本文关键词: Elasticsearch, 全文搜索, 分布式搜索, 数据分析, 实时搜索, Lucene

http://www.dtcms.com/a/314495.html

相关文章:

  • Oracle EBS ERP接口开发 — 修复bug基本流程
  • 《人形机器人的觉醒:技术革命与碳基未来》——类人关节设计:气动人工肌肉PAM及其对材料技术要求与限制
  • 安装MySQL可视化管理工具DBeaver教程
  • Armoury Crate无法通过BIOS卸载
  • 微信小程序本地存储与Cookie的区别
  • 华为OD机考2025C卷 - 开源项目热度榜单 (Java Python JS C++ C )
  • Java反射的Field
  • 消息系统技术文档
  • 抽像代数概念理解——陪集(coset)
  • 华为2288H V5服务器闪红灯 无法开机案例
  • SpringBoot整合t-io是websocket实时通信
  • 题单【排序】
  • linux中 多进程 导致cuda错误的问题解决
  • 【测试】⾃动化测试常⽤函数
  • 配置Mybatis环境
  • 向量魔法:Embedding如何赋能大模型理解世界
  • 靶场(二十八)---小白心得靶场体会---Mantis
  • 嵌入式开发学习———Linux环境下IO进程线程学习(三)
  • DolphinScheduler 集成DataX
  • 前端VUE基础环境搭建
  • opencv引入libavif
  • typeof和instanceof区别
  • 静电干扰误报率↓85%!陌讯多模态融合算法在智慧环卫检测优化
  • python基础:数据解析BeatuifulSoup,不需要考虑前端形式的一种获取元素的方法
  • 【前端】JavaScript基础知识及基本应用
  • 【AMD】编译llama.cpp实践版
  • 管家级教程:在 Windows 上配置 WSL2、CUDA 及 VLLM,开源音频克隆项目
  • 解决 Alpine 容器中编译 confluent-kafka-go 报错的问题
  • LLaMA-Factory微调教程4:模型评估
  • Spring Boot + ShardingSphere 分库分表实战