Spring Boot整合Elasticsearch案例

wen java案例 2

本文目录导读:

Spring Boot整合Elasticsearch案例

  1. 项目结构
  2. Maven依赖 (pom.xml)
  3. 配置文件 (application.yml)
  4. 启动类
  5. 实体类
  6. Repository接口
  7. Service接口
  8. Service实现类
  9. 控制器
  10. 配置类(可选)
  11. 测试数据初始化
  12. 测试类
  13. 使用说明
  14. 注意事项

我来为您提供一个完整的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"

注意事项

  1. 版本兼容性:确保Spring Boot、Spring Data Elasticsearch和Elasticsearch的版本兼容
  2. IK分词器:如果需要中文分词,需要安装IK分词器插件
  3. 索引管理:生产环境通常通过ES API手动管理索引
  4. 性能优化:大批量操作建议使用bulk API
  5. 连接管理:生产环境建议使用连接池配置

这个案例涵盖了Spring Boot整合Elasticsearch的主要功能,包括CRUD操作、分页查询、多字段搜索、组合查询等,您可以根据实际需求进行调整和扩展。

抱歉,评论功能暂时关闭!