Elasticsearch 搜索引擎与 Spring Boot 应用整合:让你的数据“嗖”一下就找到!
各位看官,大家好!今天咱们聊点儿刺激的,聊聊如何给你的 Spring Boot 应用装上一个超级搜索引擎——Elasticsearch!想象一下,你的应用数据量越来越大,用户想找个东西,得翻个底朝天,那体验简直就像在浩瀚星空中寻找一颗流星。有了 Elasticsearch,一切都变得不一样了,用户输入关键词,数据“嗖”的一下就出来了,简直比火箭还快!
本文将用通俗易懂的语言,配合大量的代码示例,手把手教你如何将 Elasticsearch 集成到你的 Spring Boot 应用中,让你的数据检索能力瞬间提升N个档次!
1. 什么是 Elasticsearch?为什么我们需要它?
Elasticsearch,江湖人称“ES”,是一个开源的、分布式的搜索和分析引擎。它基于 Lucene 构建,提供了强大的全文搜索、结构化搜索、分析以及近实时搜索能力。
你可以把它想象成一个超级强大的图书馆管理员,它不仅能记住每一本书的标题、作者、内容,还能根据你的任何关键词,迅速找到相关的书籍。
那么,为什么我们需要它呢?
- 全文搜索: 传统数据库的
LIKE
语句在面对复杂的全文搜索场景时,性能会急剧下降。ES 擅长处理海量文本数据,并提供高效的全文搜索能力。 - 近实时搜索: 数据更新后,几乎可以立即被搜索到,满足实时性要求较高的场景。
- 分布式架构: 可以轻松扩展到数百台服务器,处理 PB 级别的数据。
- 灵活的数据模型: 支持多种数据类型,包括文本、数值、日期等。
- 强大的分析功能: 可以对数据进行聚合、分析,挖掘潜在价值。
简单来说,如果你的应用需要处理大量的文本数据,并且需要快速、准确地进行搜索,那么 Elasticsearch 就是你的不二之选。
2. 准备工作:磨刀不误砍柴工
在开始之前,我们需要做一些准备工作:
-
安装 Elasticsearch: 你可以从 Elasticsearch 官网下载安装包,或者使用 Docker 进行安装。这里推荐使用 Docker,方便快捷。
docker pull docker.elastic.co/elasticsearch/elasticsearch:7.17.6 docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.17.6
这个命令会下载并启动一个 Elasticsearch 容器,并将 9200(HTTP 端口)和 9300(TCP 端口)映射到宿主机。
-
安装 Kibana (可选): Kibana 是 Elasticsearch 的可视化工具,可以方便地查看和分析数据。同样可以使用 Docker 安装。
docker pull docker.elastic.co/kibana/kibana:7.17.6 docker run -d -p 5601:5601 -e "ELASTICSEARCH_HOSTS=http://localhost:9200" docker.elastic.co/kibana/kibana:7.17.6
这个命令会下载并启动一个 Kibana 容器,并将 5601 端口映射到宿主机,并配置 Kibana 连接到 Elasticsearch。
-
创建 Spring Boot 项目: 使用 Spring Initializr 创建一个 Spring Boot 项目,并添加以下依赖:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
其中,
spring-boot-starter-data-elasticsearch
是 Spring Data Elasticsearch 的依赖,它简化了与 Elasticsearch 的交互。lombok
可以简化实体类的编写。
3. 配置 Spring Boot 连接 Elasticsearch
在 application.properties
或 application.yml
文件中,配置 Elasticsearch 的连接信息:
spring:
data:
elasticsearch:
cluster-nodes: localhost:9200 # Elasticsearch 的地址和端口
cluster-name: docker-cluster # 你的集群名称,默认是 elasticsearch
请根据你的实际情况修改 cluster-nodes
和 cluster-name
。
4. 定义数据模型 (Entity)
我们需要定义一个 Java 类来映射 Elasticsearch 中的文档。例如,我们创建一个 Product
类:
import lombok.Data;
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;
@Data
@Document(indexName = "products", shards = 1, replicas = 0)
public class Product {
@Id
private String id;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String name;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String description;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Keyword)
private String category;
}
@Document(indexName = "products", shards = 1, replicas = 0)
:指定该类映射到 Elasticsearch 中的索引名称为products
,设置 shards 和 replicas 的数量。@Id
:指定id
字段为文档的 ID。@Field(type = FieldType.Text, analyzer = "ik_max_word")
:指定name
和description
字段为文本类型,并使用ik_max_word
分析器进行分词。@Field(type = FieldType.Double)
:指定price
字段为 Double 类型。@Field(type = FieldType.Keyword)
:指定category
字段为 Keyword 类型,Keyword 类型不会进行分词。@Data
:使用了 Lombok 的注解,自动生成 getter、setter、toString 等方法。
关于 analyzer
:
analyzer
是 Elasticsearch 中用于将文本分解成词项的组件。不同的 analyzer
会产生不同的分词结果,影响搜索的准确性。
ik_max_word
:IK 分词器的一种,会将文本分解成最细粒度的词项。ik_smart
:IK 分词器的另一种,会进行智能分词,更加符合语义。standard
:Elasticsearch 默认的分词器,对英文文本效果较好,对中文文本效果较差。
为了获得更好的中文分词效果,建议使用 IK 分词器。你需要先安装 IK 分词器插件,然后才能在 @Field
注解中使用它。
安装 IK 分词器插件:
-
进入 Elasticsearch 容器:
docker exec -it <elasticsearch_container_id> bash
-
下载并安装 IK 分词器插件:
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.17.6/elasticsearch-analysis-ik-7.17.6.zip
-
重启 Elasticsearch 容器:
docker restart <elasticsearch_container_id>
5. 创建 Repository
Spring Data Elasticsearch 提供了 ElasticsearchRepository
接口,可以简化对 Elasticsearch 的操作。我们创建一个 ProductRepository
接口:
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.example.demo.entity.Product;
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
}
ElasticsearchRepository
接口提供了常用的 CRUD 操作,例如 save
、findById
、findAll
、deleteById
等。
6. 实现 CRUD 操作
现在,我们可以使用 ProductRepository
来实现对 Product
数据的 CRUD 操作。创建一个 ProductService
类:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.entity.Product;
import com.example.demo.repository.ProductRepository;
import java.util.List;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public Product save(Product product) {
return productRepository.save(product);
}
public Optional<Product> findById(String id) {
return productRepository.findById(id);
}
public List<Product> findAll() {
return (List<Product>) productRepository.findAll();
}
public void deleteById(String id) {
productRepository.deleteById(id);
}
}
这个 ProductService
类提供了 save
、findById
、findAll
、deleteById
等方法,用于操作 Product
数据。
7. 创建 Controller
为了方便测试,我们创建一个 ProductController
类,提供 RESTful API 接口:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping
public Product save(@RequestBody Product product) {
return productService.save(product);
}
@GetMapping("/{id}")
public Optional<Product> findById(@PathVariable String id) {
return productService.findById(id);
}
@GetMapping
public List<Product> findAll() {
return productService.findAll();
}
@DeleteMapping("/{id}")
public void deleteById(@PathVariable String id) {
productService.deleteById(id);
}
}
这个 ProductController
类提供了 POST
、GET
、DELETE
等 HTTP 方法,用于操作 Product
数据。
8. 简单测试
启动 Spring Boot 应用,使用 Postman 或 curl 等工具进行测试。
-
新增数据:
curl -X POST http://localhost:8080/products -H 'Content-Type: application/json' -d '{ "name": "小米手机13", "description": "性能怪兽,拍照神器", "price": 4999.0, "category": "手机" }'
-
查询数据:
curl http://localhost:8080/products/1
-
查询所有数据:
curl http://localhost:8080/products
-
删除数据:
curl -X DELETE http://localhost:8080/products/1
9. 实现搜索功能
除了基本的 CRUD 操作,我们还需要实现搜索功能。Spring Data Elasticsearch 提供了多种搜索方式:
- Query Methods: 通过方法名定义查询条件。
- @Query 注解: 使用 Elasticsearch 的 Query DSL 定义查询条件。
- ElasticsearchTemplate: 使用 ElasticsearchTemplate 提供的 API 进行查询。
9.1 使用 Query Methods 实现搜索
Query Methods 是 Spring Data Elasticsearch 提供的一种简单易用的搜索方式。我们只需要在 ProductRepository
接口中定义方法名,Spring Data Elasticsearch 就会自动生成对应的查询语句。
例如,我们可以定义一个 findByNameContaining
方法,用于根据商品名称进行模糊搜索:
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.example.demo.entity.Product;
import java.util.List;
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
List<Product> findByNameContaining(String name);
}
然后在 ProductService
类中添加一个 searchByName
方法:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.entity.Product;
import com.example.demo.repository.ProductRepository;
import java.util.List;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public Product save(Product product) {
return productRepository.save(product);
}
public Optional<Product> findById(String id) {
return productRepository.findById(id);
}
public List<Product> findAll() {
return (List<Product>) productRepository.findAll();
}
public void deleteById(String id) {
productRepository.deleteById(id);
}
public List<Product> searchByName(String name) {
return productRepository.findByNameContaining(name);
}
}
最后,在 ProductController
类中添加一个 searchByName
接口:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping
public Product save(@RequestBody Product product) {
return productService.save(product);
}
@GetMapping("/{id}")
public Optional<Product> findById(@PathVariable String id) {
return productService.findById(id);
}
@GetMapping
public List<Product> findAll() {
return productService.findAll();
}
@DeleteMapping("/{id}")
public void deleteById(@PathVariable String id) {
productService.deleteById(id);
}
@GetMapping("/search")
public List<Product> searchByName(@RequestParam String name) {
return productService.searchByName(name);
}
}
现在,你可以使用以下命令进行搜索:
curl http://localhost:8080/products/search?name=小米
9.2 使用 @Query 注解实现搜索
@Query
注解允许我们使用 Elasticsearch 的 Query DSL 定义查询条件。这种方式更加灵活,可以实现复杂的搜索需求。
例如,我们可以定义一个 searchByNameAndDescription
方法,用于根据商品名称和描述进行模糊搜索:
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.example.demo.entity.Product;
import java.util.List;
public interface ProductRepository extends ElasticsearchRepository<Product, String> {
@Query("{"bool": {"should": [{"match": {"name": "?0"}}, {"match": {"description": "?0"}}]}}")
List<Product> searchByNameAndDescription(String keyword);
}
然后在 ProductService
类中添加一个 searchByNameAndDescription
方法:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.entity.Product;
import com.example.demo.repository.ProductRepository;
import java.util.List;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public Product save(Product product) {
return productRepository.save(product);
}
public Optional<Product> findById(String id) {
return productRepository.findById(id);
}
public List<Product> findAll() {
return (List<Product>) productRepository.findAll();
}
public void deleteById(String id) {
productRepository.deleteById(id);
}
public List<Product> searchByName(String name) {
return productRepository.findByNameContaining(name);
}
public List<Product> searchByNameAndDescription(String keyword) {
return productRepository.searchByNameAndDescription(keyword);
}
}
最后,在 ProductController
类中添加一个 searchByNameAndDescription
接口:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping
public Product save(@RequestBody Product product) {
return productService.save(product);
}
@GetMapping("/{id}")
public Optional<Product> findById(@PathVariable String id) {
return productService.findById(id);
}
@GetMapping
public List<Product> findAll() {
return productService.findAll();
}
@DeleteMapping("/{id}")
public void deleteById(@PathVariable String id) {
productService.deleteById(id);
}
@GetMapping("/search2")
public List<Product> searchByNameAndDescription(@RequestParam String keyword) {
return productService.searchByNameAndDescription(keyword);
}
}
现在,你可以使用以下命令进行搜索:
curl http://localhost:8080/products/search2?keyword=手机
9.3 使用 ElasticsearchTemplate 实现搜索
ElasticsearchTemplate
提供了更加底层的 API,可以实现更复杂的搜索需求。
首先,在 Spring Boot 容器中注入 ElasticsearchTemplate
:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.stereotype.Service;
import com.example.demo.entity.Product;
import com.example.demo.repository.ProductRepository;
import java.util.List;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
public Product save(Product product) {
return productRepository.save(product);
}
public Optional<Product> findById(String id) {
return productRepository.findById(id);
}
public List<Product> findAll() {
return (List<Product>) productRepository.findAll();
}
public void deleteById(String id) {
productRepository.deleteById(id);
}
public List<Product> searchByName(String name) {
return productRepository.findByNameContaining(name);
}
public List<Product> searchByNameAndDescription(String keyword) {
return productRepository.searchByNameAndDescription(keyword);
}
}
然后,在 ProductService
类中添加一个 searchByTemplate
方法:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import com.example.demo.entity.Product;
import com.example.demo.repository.ProductRepository;
import static org.elasticsearch.index.query.QueryBuilders.*;
import java.util.List;
import java.util.Optional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
public Product save(Product product) {
return productRepository.save(product);
}
public Optional<Product> findById(String id) {
return productRepository.findById(id);
}
public List<Product> findAll() {
return (List<Product>) productRepository.findAll();
}
public void deleteById(String id) {
productRepository.deleteById(id);
}
public List<Product> searchByName(String name) {
return productRepository.findByNameContaining(name);
}
public List<Product> searchByNameAndDescription(String keyword) {
return productRepository.searchByNameAndDescription(keyword);
}
public List<Product> searchByTemplate(String keyword) {
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery()
.should(matchQuery("name", keyword))
.should(matchQuery("description", keyword)))
.build();
return elasticsearchTemplate.queryForList(searchQuery, Product.class);
}
}
最后,在 ProductController
类中添加一个 searchByTemplate
接口:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/products")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping
public Product save(@RequestBody Product product) {
return productService.save(product);
}
@GetMapping("/{id}")
public Optional<Product> findById(@PathVariable String id) {
return productService.findById(id);
}
@GetMapping
public List<Product> findAll() {
return productService.findAll();
}
@DeleteMapping("/{id}")
public void deleteById(@PathVariable String id) {
productService.deleteById(id);
}
@GetMapping("/search3")
public List<Product> searchByTemplate(@RequestParam String keyword) {
return productService.searchByTemplate(keyword);
}
}
现在,你可以使用以下命令进行搜索:
curl http://localhost:8080/products/search3?keyword=手机
10. 总结
本文详细介绍了如何将 Elasticsearch 集成到 Spring Boot 应用中,并实现了基本的 CRUD 操作和搜索功能。通过学习本文,你应该能够:
- 理解 Elasticsearch 的基本概念和优势。
- 配置 Spring Boot 连接 Elasticsearch。
- 定义数据模型 (Entity) 映射到 Elasticsearch 中的文档。
- 使用
ElasticsearchRepository
实现 CRUD 操作。 - 使用 Query Methods、
@Query
注解和ElasticsearchTemplate
实现搜索功能。
当然,Elasticsearch 的功能远不止这些,例如:
- 聚合分析: 可以对数据进行聚合、分组、统计等操作,挖掘潜在价值。
- 地理位置搜索: 可以根据地理位置信息进行搜索。
- 自动补全: 可以根据用户输入的内容,自动提示可能的搜索关键词。
希望本文能帮助你入门 Elasticsearch,并在你的实际项目中发挥它的强大作用! 祝你编程愉快,早日成为 Elasticsearch 大师!