springboot(spring-boot-starter-data-elasticsearch)操作es

环境:

springboot:2.3

es:6.8.5

jdk:21

 

需要提前创建好index

首先创建一个空的index(没有任何字段信息)
curl -u elastic:elastic -X PUT "192.168.1.192:19200/product?pretty" -H 'Content-Type: application/json' -d'
{}
'

再定义字段信息
curl -u elastic:elastic -H 'Content-Type: application/json' -XPOST "http://192.168.1.192:19200/product/product_type/_mapping?pretty" -d ' 
{
        "properties" : {
          "category" : {
            "type" : "keyword"
          },
          "description" : {
            "type" : "text"
          },
          "id" : {
            "type" : "keyword"
          },
          "price" : {
            "type" : "double"
          },
          "productName" : {
            "type" : "text"
          },
          "stock" : {
            "type" : "integer"
          }
        }
}'

 

 

 

 

1.项目结构

image

 

2.pom.xml文件

<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>

  <groupId>org.hxl</groupId>
  <artifactId>springboot_es</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>springboot_es</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.12.RELEASE</version>
    <relativePath/>
  </parent>

  <dependencies>

    <!-- SpringBoot Web 核心依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- ES6.8 Rest客户端核心依赖 (SpringBoot2.3原生适配,无需指定版本) -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>

    <!-- lombok 简化实体类 -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.30</version>
      <optional>true</optional>
    </dependency>

    <!-- fastjson 实体类转JSON字符串 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>2.0.32</version>
    </dependency>

    <dependency>
      <groupId>javax.annotation</groupId>
      <artifactId>javax.annotation-api</artifactId>
      <version>1.3.2</version>
    </dependency>

    <!-- 测试依赖 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- ========== 新增:手动指定内嵌Tomcat依赖,解决JDK21下Web容器工厂缺失 ========== -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <scope>compile</scope>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <excludes>
            <exclude>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
            </exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

 

3.配置文件application.yml

# ============ Elasticsearch 6.8 核心配置 (带账号密码+Restful方式) ============
elasticsearch:
  # ES的Rest地址,集群用英文逗号分隔,格式:http://ip:9200
  rest-addresses: http://192.168.1.134:19200
  # ES账号(ES6.8默认超级管理员账号:elastic)
  username: elastic
  # ES密码(你的ES6.8设置的密码)
  password: elastic
  # 连接超时时间
  connect-timeout: 5000
  # 读取超时时间
  socket-timeout: 30000

 

4.各java文件

ElasticsearchConfig.java

package org.hxl.config;

import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * ES6.8 RestHighLevelClient 配置类
 * 方式:Restful HTTP 9200端口 + 账号密码认证,生产环境推荐方案
 */
@Configuration
public class ElasticsearchConfig {

    @Value("${elasticsearch.rest-addresses}")
    private String restAddresses;

    @Value("${elasticsearch.username}")
    private String username;

    @Value("${elasticsearch.password}")
    private String password;

    @Value("${elasticsearch.connect-timeout}")
    private int connectTimeout;

    @Value("${elasticsearch.socket-timeout}")
    private int socketTimeout;

    /**
     * 注入带账号密码的RestHighLevelClient核心客户端
     */
    @Bean
    public RestHighLevelClient restHighLevelClient() {
        // 1. 账号密码认证核心配置
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));

        // 2. 解析ES地址
        String[] addressArr = restAddresses.split(",");
        HttpHost[] httpHosts = new HttpHost[addressArr.length];
        for (int i = 0; i < addressArr.length; i++) {
            String address = addressArr[i].replace("http://", "");
            String[] hostAndPort = address.split(":");
            String host = hostAndPort[0];
            int port = Integer.parseInt(hostAndPort[1]);
            httpHosts[i] = new HttpHost(host, port, "http");
        }

        // 3. 创建客户端并配置账号密码+超时时间
        RestClientBuilder builder = RestClient.builder(httpHosts)
                .setHttpClientConfigCallback(httpClientBuilder -> {
                    httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                    return httpClientBuilder;
                })
                .setRequestConfigCallback(requestConfigBuilder -> {
                    requestConfigBuilder.setConnectTimeout(connectTimeout);
                    requestConfigBuilder.setSocketTimeout(socketTimeout);
                    return requestConfigBuilder;
                });
        return new RestHighLevelClient(builder);
    }
}

 

ProductController.java

package org.hxl.controller;

import org.hxl.entity.Product;
import org.hxl.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/es/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    /**
     * 新增商品
     */
    @PostMapping("/add")
    public ResponseEntity<String> add(@RequestBody Product product) throws IOException {
        boolean flag = productService.addProduct(product);
        System.out.println(flag);
        return flag ? ResponseEntity.ok("新增成功") : ResponseEntity.ok("新增失败");
    }

    /**
     * 根据ID查询商品
     */
    @GetMapping("/get/{id}")
    public ResponseEntity<String> getById(@PathVariable String id) throws IOException {
        String product = productService.getProductById(id);
        return ResponseEntity.ok(product);
    }

    /**
     * 修改商品
     */
    @PostMapping("/update/{id}")
    public ResponseEntity<String> update(@PathVariable String id, @RequestBody Map<String, Object> updateMap) throws IOException {
        boolean flag = productService.updateProduct(id, updateMap);
        return flag ? ResponseEntity.ok("修改成功") : ResponseEntity.ok("修改失败");
    }

    /**
     * 删除商品
     */
    @GetMapping("/delete/{id}")
    public ResponseEntity<String> delete(@PathVariable String id) throws IOException {
        boolean flag = productService.deleteProduct(id);
        return flag ? ResponseEntity.ok("删除成功") : ResponseEntity.ok("删除失败");
    }

    /**
     * 批量新增商品
     */
    @PostMapping("/batchAdd")
    public ResponseEntity<String> batchAdd(@RequestBody List<Map<String, Object>> productList) throws IOException {
        boolean flag = productService.batchAddProduct(productList);
        return flag ? ResponseEntity.ok("批量新增成功") : ResponseEntity.ok("批量新增失败");
    }

    /**
     * 商品名称分词搜索+分页
     */
    @GetMapping("/search")
    public ResponseEntity<List<String>> search(
            @RequestParam String keyword,
            @RequestParam(defaultValue = "1") int pageNum,
            @RequestParam(defaultValue = "10") int pageSize) throws IOException {
        List<String> list = productService.searchProduct(keyword, pageNum, pageSize);
        return ResponseEntity.ok(list);
    }
}

 

Product.java

package org.hxl.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 商品实体类,对应ES的product索引文档
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    // 商品ID,对应ES文档的_id
    private String id;
    // 商品名称-分词查询
    private String productName;
    // 商品分类-不分词精准匹配
    private String category;
    // 商品价格
    private Double price;
    // 商品库存
    private Integer stock;
    // 商品描述-分词查询
    private String description;
}

 

ProductServiceImpl.java

package org.hxl.service.impl;

import org.hxl.entity.Product;
import org.hxl.service.ProductService;
import org.hxl.util.EsRestUtil;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;
import java.util.Map;

@Service
public class ProductServiceImpl implements ProductService {
    // 固定ES6.8的索引名和类型名
    private static final String ES_INDEX = "product";
    private static final String ES_TYPE = "product_type";

    @Autowired
    private EsRestUtil esRestUtil;

    @Override
    public boolean addProduct(Product product) throws IOException {
        return esRestUtil.addDoc(ES_INDEX, ES_TYPE, product.getId(), product);
    }

    @Override
    public String getProductById(String id) throws IOException {
        return esRestUtil.getDocById(ES_INDEX, ES_TYPE, id);
    }

    @Override
    public boolean updateProduct(String id, Map<String, Object> updateMap) throws IOException {
        return esRestUtil.updateDoc(ES_INDEX, ES_TYPE, id, updateMap);
    }

    @Override
    public boolean deleteProduct(String id) throws IOException {
        return esRestUtil.deleteDocById(ES_INDEX, ES_TYPE, id);
    }

    @Override
    public boolean batchAddProduct(List<Map<String, Object>> productList) throws IOException {
        return esRestUtil.batchAddDoc(ES_INDEX, ES_TYPE, productList);
    }

    @Override
    public List<String> searchProduct(String keyword, int pageNum, int pageSize) throws IOException {
        // 商品名称分词匹配,ES6.8标准语法
        MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("productName", keyword);
        return esRestUtil.searchDoc(ES_INDEX, ES_TYPE, queryBuilder, pageNum, pageSize);
    }
}

 

ProductService.java

package org.hxl.service;

import org.hxl.entity.Product;

import java.io.IOException;
import java.util.List;
import java.util.Map;

public interface ProductService {
    // 新增商品
    boolean addProduct(Product product) throws IOException;
    // 根据ID查询商品
    String getProductById(String id) throws IOException;
    // 根据ID修改商品
    boolean updateProduct(String id, Map<String, Object> updateMap) throws IOException;
    // 根据ID删除商品
    boolean deleteProduct(String id) throws IOException;
    // 批量新增商品
    boolean batchAddProduct(List<Map<String, Object>> productList) throws IOException;
    // 商品名称分词搜索+分页
    List<String> searchProduct(String keyword, int pageNum, int pageSize) throws IOException;
}

 

EsRestUtil.java

package org.hxl.util;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * ES6.8 REST客户端通用工具类
 * ? 适配:JDK21 + SpringBoot2.3 + 账号密码认证
 * ? 全坑修复:参数缺失+序列化异常+JDK21兼容
 * ? ES6.8规则:index + type 必须同时指定
 */
@Slf4j
@Component
public class EsRestUtil {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * 新增文档
     */
    public boolean addDoc(String index, String type, String docId, Object data) throws IOException {
        IndexRequest request = new IndexRequest(index, type, docId);
        request.source(JSON.toJSONString(data), XContentType.JSON);
        IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        return "CREATED".equals(response.getResult().name()) || "UPDATED".equals(response.getResult().name());
    }

    /**
     * 根据ID修改文档
     */
    public boolean updateDoc(String index, String type, String docId, Map<String, Object> updateMap) throws IOException {
        UpdateRequest request = new UpdateRequest(index, type, docId);
        request.doc(JSON.toJSONString(updateMap), XContentType.JSON);
        UpdateResponse response = restHighLevelClient.update(request, RequestOptions.DEFAULT);
        return response.status().getStatus() == 200;
    }

    /**
     * 根据ID查询文档
     */
    public String getDocById(String index, String type, String docId) throws IOException {
        GetRequest request = new GetRequest(index, type, docId);
        GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);
        return response.isExists() ? response.getSourceAsString() : null;
    }

    /**
     * 根据ID删除文档
     */
    public boolean deleteDocById(String index, String type, String docId) throws IOException {
        DeleteRequest request = new DeleteRequest(index, type, docId);
        DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        return response.status().getStatus() == 200;
    }

    /**
     * 批量新增文档
     */
    public boolean batchAddDoc(String index, String type, List<Map<String, Object>> dataList) throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        dataList.forEach(map -> {
            String docId = map.get("id").toString();
            bulkRequest.add(new IndexRequest(index, type, docId)
                    .source(JSON.toJSONString(map), XContentType.JSON));
        });
        BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        return !bulkResponse.hasFailures();
    }

    /**
     * 条件分页查询文档 (ES6.8核心查询方式)
     */
    public List<String> searchDoc(String index, String type, QueryBuilder queryBuilder, int pageNum, int pageSize) throws IOException {
        SearchRequest searchRequest = new SearchRequest(index);
        searchRequest.types(type); // ES6.8必须指定type

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(queryBuilder);
        sourceBuilder.from((pageNum - 1) * pageSize);
        sourceBuilder.size(pageSize);
        searchRequest.source(sourceBuilder);

        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        SearchHit[] hits = response.getHits().getHits();
        List<String> resultList = new ArrayList<>();
        for (SearchHit hit : hits) {
            resultList.add(hit.getSourceAsString());
        }
        return resultList;
    }
}

 

App.java

package org.hxl;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

/**
 * 修复点1:继承 SpringBootServletInitializer 解决Web容器工厂Bean缺失问题
 * 修复点2:保留原有JDK21+ES6.8的核心配置,缺一不可
 * 修复点3:@SpringBootApplication 注解保留,自动扫描包
 */
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        // 1. 解决ES6.8 netty冲突 必加
        //System.setProperty("es.set.netty.runtime.available.processors", "false");
        // 2. 解决JDK21反射访问限制 必加
        //System.setProperty("jdk.reflect.useDirectMethodHandle", "false");
        // 3. 启动项目
        SpringApplication.run(App.class, args);
        System.out.println("===== Spring Boot 启动成功 =====");
    }
}

 

 

 

 

 

 

5.调用

http://localhost:8080/es/product/add

image

 请求报文

{
    "id": "1005",
    "productName": "小米15 骁龙8Gen4",
    "category": "手机",
    "price": 5299.0,
    "stock": 5000,
    "description": "澎湃OS系统"
}

 

es里查看

curl -u elastic:elastic -H "Content-Type: application/json" -XGET '192.168.1.134:19200/product/_search?pretty' -d '
{
"query": { "match_all": {} }
}'

posted @ 2026-01-15 10:06  slnngk  阅读(4)  评论(0)    收藏  举报