本文目录导读:

- 项目结构
- Maven依赖 (pom.xml)
- 配置文件 (application.yml)
- 启动类
- 实体类
- Repository接口
- Service接口
- Service实现类
- 控制器
- 配置类(可选)
- 测试数据初始化
- 测试类
- 使用说明
- 注意事项
我来为您提供一个完整的Spring Boot整合Elasticsearch的实战案例。
项目结构
spring-boot-elasticsearch/
├── pom.xml
├── src/main/java/com/example/es/
│ ├── EsApplication.java
│ ├── config/
│ │ └── ElasticsearchConfig.java
│ ├── entity/
│ │ └── Product.java
│ ├── repository/
│ │ └── ProductRepository.java
│ ├── service/
│ │ ├── ProductService.java
│ │ └── impl/ProductServiceImpl.java
│ └── controller/
│ └── ProductController.java
└── src/main/resources/
└── application.yml
Maven依赖 (pom.xml)
<?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.7.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-boot-elasticsearch</artifactId>
<version>1.0.0</version>
<name>spring-boot-elasticsearch</name>
<description>Spring Boot Elasticsearch Demo</description>
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.17.9</elasticsearch.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Data Elasticsearch -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
配置文件 (application.yml)
server:
port: 8080
spring:
application:
name: spring-boot-elasticsearch
elasticsearch:
uris: http://localhost:9200
connection-timeout: 1s
socket-timeout: 30s
data:
elasticsearch:
repositories:
enabled: true
logging:
level:
org.springframework.data.elasticsearch: DEBUG
com.example.es: DEBUG
启动类
package com.example.es;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
@SpringBootApplication
@EnableElasticsearchRepositories
public class EsApplication {
public static void main(String[] args) {
SpringApplication.run(EsApplication.class, args);
}
}
实体类
package com.example.es.entity;
import lombok.AllArgsConstructor;
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;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Document(indexName = "products", createIndex = true)
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;
@Field(type = FieldType.Integer)
private Integer stock;
@Field(type = FieldType.Date)
private String createTime;
@Field(type = FieldType.Boolean)
private Boolean status;
}
Repository接口
package com.example.es.repository;
import com.example.es.entity.Product;
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 ProductRepository extends ElasticsearchRepository<Product, String> {
// 根据名称模糊查询
List<Product> findByName(String name);
// 根据名称和分类查询
List<Product> findByNameAndCategory(String name, String category);
// 根据价格区间查询
List<Product> findByPriceBetween(Double min, Double max);
// 根据分类分页查询
Page<Product> findByCategory(String category, Pageable pageable);
// 多字段搜索
@Query("{\"multi_match\": {\"query\": \"?0\", \"fields\": [\"name\", \"description\"]}}")
Page<Product> searchMultiField(String keyword, Pageable pageable);
// 精确查询
@Query("{\"bool\": {\"must\": [{\"term\": {\"category.keyword\": \"?0\"}}]}}")
List<Product> findByCategoryExact(String category);
// 组合查询
@Query("{\"bool\": {\"must\": [{\"match\": {\"name\": \"?0\"}}], " +
"\"filter\": [{\"range\": {\"price\": {\"gte\": ?1}}}], " +
"\"must_not\": [{\"term\": {\"status\": false}}]}}")
List<Product> customSearch(String keyword, Double minPrice);
}
Service接口
package com.example.es.service;
import com.example.es.entity.Product;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
import java.util.Optional;
public interface ProductService {
// 保存单个商品
Product save(Product product);
// 批量保存
Iterable<Product> saveAll(List<Product> products);
// 根据ID查询
Optional<Product> findById(String id);
// 查询所有
Iterable<Product> findAll();
// 分页查询
Page<Product> findByPage(Pageable pageable);
// 根据ID删除
void deleteById(String id);
// 删除所有
void deleteAll();
// 根据名称搜索
List<Product> searchByName(String name);
// 多字段搜索
Page<Product> search(String keyword, Pageable pageable);
// 价格区间查询
List<Product> findByPriceBetween(Double min, Double max);
// 组合查询
List<Product> customSearch(String keyword, Double minPrice);
}
Service实现类
package com.example.es.service.impl;
import com.example.es.entity.Product;
import com.example.es.repository.ProductRepository;
import com.example.es.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductRepository productRepository;
@Override
public Product save(Product product) {
return productRepository.save(product);
}
@Override
public Iterable<Product> saveAll(List<Product> products) {
return productRepository.saveAll(products);
}
@Override
public Optional<Product> findById(String id) {
return productRepository.findById(id);
}
@Override
public Iterable<Product> findAll() {
return productRepository.findAll();
}
@Override
public Page<Product> findByPage(Pageable pageable) {
return productRepository.findAll(pageable);
}
@Override
public void deleteById(String id) {
productRepository.deleteById(id);
}
@Override
public void deleteAll() {
productRepository.deleteAll();
}
@Override
public List<Product> searchByName(String name) {
return productRepository.findByName(name);
}
@Override
public Page<Product> search(String keyword, Pageable pageable) {
return productRepository.searchMultiField(keyword, pageable);
}
@Override
public List<Product> findByPriceBetween(Double min, Double max) {
return productRepository.findByPriceBetween(min, max);
}
@Override
public List<Product> customSearch(String keyword, Double minPrice) {
return productRepository.customSearch(keyword, minPrice);
}
}
控制器
package com.example.es.controller;
import com.example.es.entity.Product;
import com.example.es.service.ProductService;
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.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@RestController
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductService productService;
// 添加商品
@PostMapping
public ResponseEntity<Product> addProduct(@RequestBody Product product) {
Product saved = productService.save(product);
return ResponseEntity.ok(saved);
}
// 批量添加
@PostMapping("/batch")
public ResponseEntity<Iterable<Product>> addProducts(@RequestBody List<Product> products) {
return ResponseEntity.ok(productService.saveAll(products));
}
// 更新商品
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(@PathVariable String id, @RequestBody Product product) {
product.setId(id);
return ResponseEntity.ok(productService.save(product));
}
// 根据ID查询
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable String id) {
Optional<Product> product = productService.findById(id);
return product.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// 查询所有
@GetMapping
public ResponseEntity<Iterable<Product>> getAllProducts() {
return ResponseEntity.ok(productService.findAll());
}
// 分页查询
@GetMapping("/page")
public ResponseEntity<Page<Product>> getProductsByPage(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "createTime") String sort,
@RequestParam(defaultValue = "desc") String direction) {
Sort sortBy = Sort.by(Sort.Direction.fromString(direction), sort);
Pageable pageable = PageRequest.of(page, size, sortBy);
return ResponseEntity.ok(productService.findByPage(pageable));
}
// 搜索
@GetMapping("/search")
public ResponseEntity<Map<String, Object>> search(
@RequestParam String keyword,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size) {
Pageable pageable = PageRequest.of(page, size);
Page<Product> result = productService.search(keyword, pageable);
Map<String, Object> response = new HashMap<>();
response.put("products", result.getContent());
response.put("total", result.getTotalElements());
response.put("totalPages", result.getTotalPages());
return ResponseEntity.ok(response);
}
// 根据名称搜索
@GetMapping("/search-by-name")
public ResponseEntity<List<Product>> searchByName(@RequestParam String name) {
return ResponseEntity.ok(productService.searchByName(name));
}
// 价格区间查询
@GetMapping("/price-range")
public ResponseEntity<List<Product>> getByPriceRange(
@RequestParam Double min,
@RequestParam Double max) {
return ResponseEntity.ok(productService.findByPriceBetween(min, max));
}
// 组合查询
@GetMapping("/custom-search")
public ResponseEntity<List<Product>> customSearch(
@RequestParam String keyword,
@RequestParam Double minPrice) {
return ResponseEntity.ok(productService.customSearch(keyword, minPrice));
}
// 删除商品
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteProduct(@PathVariable String id) {
productService.deleteById(id);
return ResponseEntity.ok().build();
}
// 清空所有数据
@DeleteMapping("/all")
public ResponseEntity<Void> deleteAllProducts() {
productService.deleteAll();
return ResponseEntity.ok().build();
}
}
配置类(可选)
package com.example.es.config;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
@Configuration
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
@Override
public RestHighLevelClient elasticsearchClient() {
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.withConnectTimeout(1000)
.withSocketTimeout(30000)
.build();
return RestClients.create(clientConfiguration).rest();
}
}
测试数据初始化
package com.example.es.config;
import com.example.es.entity.Product;
import com.example.es.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@Component
public class DataInitializer implements CommandLineRunner {
@Autowired
private ProductRepository productRepository;
@Override
public void run(String... args) throws Exception {
// 检查索引是否存在
boolean exists = productRepository.exists();
if (!exists) {
// 创建索引
productRepository.indexOps().create();
// 初始化测试数据
List<Product> products = new ArrayList<>();
for (int i = 0; i < 100; i++) {
Product product = new Product();
product.setId(UUID.randomUUID().toString());
product.setName("测试商品" + i);
product.setDescription("这是第" + i + "个测试商品的详细描述");
product.setPrice(10.0 + i * 10);
product.setCategory(i % 3 == 0 ? "电子产品" : (i % 3 == 1 ? "服装" : "食品"));
product.setStock(100 - i);
product.setCreateTime(String.valueOf(System.currentTimeMillis()));
product.setStatus(i % 5 != 0);
products.add(product);
}
productRepository.saveAll(products);
System.out.println("Index 'products' created and test data inserted.");
}
}
}
测试类
package com.example.es;
import com.example.es.entity.Product;
import com.example.es.repository.ProductRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import java.util.List;
import java.util.Optional;
@SpringBootTest
class EsApplicationTests {
@Autowired
private ProductRepository productRepository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Test
void testSave() {
Product product = new Product();
product.setId("test001");
product.setName("测试商品");
product.setDescription("测试描述");
product.setPrice(99.9);
product.setCategory("测试分类");
product.setStock(50);
product.setCreateTime(String.valueOf(System.currentTimeMillis()));
product.setStatus(true);
productRepository.save(product);
System.out.println("保存成功: " + product);
}
@Test
void testFindById() {
Optional<Product> product = productRepository.findById("test001");
if (product.isPresent()) {
System.out.println("找到商品: " + product.get());
} else {
System.out.println("未找到商品");
}
}
@Test
void testSearch() {
Page<Product> products = productRepository.searchMultiField("测试",
PageRequest.of(0, 10));
System.out.println("搜索到 " + products.getTotalElements() + " 条记录");
products.forEach(p -> System.out.println(p));
}
@Test
void testFindByCategory() {
Page<Product> products = productRepository.findByCategory("电子产品",
PageRequest.of(0, 5));
System.out.println("电子产品数量: " + products.getTotalElements());
}
}
使用说明
启动Elasticsearch
# 启动Elasticsearch服务(需要先安装) ./bin/elasticsearch
测试API
# 添加商品
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{
"name": "iPhone 15",
"description": "Apple最新款手机",
"price": 5999.00,
"category": "电子产品",
"stock": 100,
"status": true
}'
# 搜索商品
curl "http://localhost:8080/api/products/search?keyword=手机&page=0&size=10"
# 价格区间查询
curl "http://localhost:8080/api/products/price-range?min=100&max=1000"
# 分页查询
curl "http://localhost:8080/api/products/page?page=0&size=5&sort=price&direction=desc"
注意事项
- 版本兼容性:确保Spring Boot、Spring Data Elasticsearch和Elasticsearch的版本兼容
- IK分词器:如果需要中文分词,需要安装IK分词器插件
- 索引管理:生产环境通常通过ES API手动管理索引
- 性能优化:大批量操作建议使用bulk API
- 连接管理:生产环境建议使用连接池配置
这个案例涵盖了Spring Boot整合Elasticsearch的主要功能,包括CRUD操作、分页查询、多字段搜索、组合查询等,您可以根据实际需求进行调整和扩展。