整合Jdk17+Spring Boot3.2+Elasticsearch9.0+mybatis3.5.12的简单用法

Elasticsearch是一个基于Lucene的分布式搜索和分析引擎,广泛应用于全文搜索、日志分析等场景。结合Spring Boot可以快速构建强大的搜索应用。本文将介绍如何在Spring Boot项目中集成和使用Elasticsearch。

ES9.0.1目前支持的包只有

复制代码
elasticsearch-rest-client/                                       -         -      
elasticsearch-rest-client-sniffer/  

注意:9.0版本暂不支持elasticsearch-rest-high-level-client

所以我们要用elasticsearch-rest-client来进行开发。

一、环境准备

1. 添加依赖

首先,在Spring Boot项目的`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>

    <groupId>org.example</groupId>
    <artifactId>EsExample</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>

        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <mybatisplus.version>3.5.12</mybatisplus.version>

    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        </dependency>

<!--        &lt;!&ndash; MyBatis集成 &ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>org.mybatis.spring.boot</groupId>-->
<!--            <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!--            <version>${mybatis.version}</version>-->
<!--        </dependency>-->

        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.23</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>3.3.4</version>
        </dependency>
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
            <version>3.1.6</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>6.2.5.Final</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
            <version>${mybatisplus.version}</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>9.0.1</version>
        </dependency>
        <dependency>
            <groupId>co.elastic.clients</groupId>
            <artifactId>elasticsearch-java</artifactId>
            <version>9.0.1</version>
        </dependency>
        <dependency>
            <groupId>jakarta.json</groupId>
            <artifactId>jakarta.json-api</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.16.0</version>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>1.21.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-compress</artifactId>
            <version>1.21</version>
        </dependency>
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>testcontainers</artifactId>
            <version>1.21.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.slugify</groupId>
            <artifactId>slugify</artifactId>
            <version>3.0.6</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.apache.httpcomponents</groupId>-->
<!--            <artifactId>httpclient</artifactId>-->
<!--            <version>4.5.13</version>-->
<!--        </dependency>-->
        <dependency>
            <groupId>jakarta.json.bind</groupId>
            <artifactId>jakarta.json.bind-api</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse</groupId>
            <artifactId>yasson</artifactId>
            <version>3.0.4</version>
        </dependency>

    </dependencies>
    <dependencyManagement>

        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>3.2.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
                <version>${mybatisplus.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <repositories>
        <repository>
            <id>aliyun</id>
            <url>https://maven.aliyun.com/nexus/content/groups/public</url>
        </repository>
        <repository>
            <id>oss-public</id>
            <url>https://oss.sonatype.org/content/repositories/public</url>
        </repository>
        <repository>
            <id>snapshots</id>
            <url>https://central.sonatype.com/repository/maven-snapshots/</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
</project>

2. 配置Elasticsearch连接

在 application.yml 中配置Elasticsearch连接,pgsql连接:

Elasticsearch配置

复制代码
spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/postgres?stringtype=unspecified
    username: postgres
    password: 123456
  jpa:
    hibernate:
      ddl-auto: update  #自动生成数据库表
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
    show-sql: true # jpa配置,在控制台显示hibernate的sql

server:
  port: 8904
  servlet:
    encoding:
      charset: UTF-8

elasticsearch:
  server:
    url: https://localhost:9200
  api:
    key: YL5i65YB6ixG4DLbphIW
  userName:  elastic
  password:  _XRuyepioTeZGzQOhUfk
  host: localhost
  port: 9200

logging:
  level:
    org.example.mapper: debug


file:
  path: D://testaa/img/

二、基本用法

1. 定义实体类

使用 Table 来注解标记表名称以及es的索引:

@Id注解用于es的id

复制代码
/**
 * 测试日期
 *
 * @author lyl
 * @version v1.0
 * @since 2025/5/26
 */
@Data
@Table(name="sys_base_data")
@TableName("sys_base_data")
public class StockBaseEntity implements Serializable {
    @Id
    private String id;


    private String name;



    @TableField(fill= FieldFill.INSERT)
    @JsonFormat(shape= JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd",timezone = "GMT+8")
    private String createDate;

    @TableField(fill= FieldFill.INSERT)
    @JsonFormat(shape=JsonFormat.Shape.STRING,pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date updateDate;
}

2. 创建ES连接对象

Spring Data Elasticsearch提供了类似于JPA的Repository接口:

复制代码
package org.example.es.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.message.BasicHeader;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;


@Configuration
public class ElasticClient {

    @Value("${elasticsearch.server.url}")
    private String serverUrl;

    @Value("${elasticsearch.api.key}")
    private String apiKey;

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

    @Value("${elasticsearch.port}")
    private int port;

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

    @Autowired
    ResourceLoader resourceLoader;

    @Bean
    public ElasticsearchClient elasticRestClient() throws IOException {
        // ES服务器URL
        // Connection settings
        try {
            HttpHost httphost = new HttpHost(host, port, "https");
            SSLContext sslContext = createInsecureSSLContext();


            BasicCredentialsProvider credsProv = new BasicCredentialsProvider();
            credsProv.setCredentials(
                    AuthScope.ANY, new UsernamePasswordCredentials(userName, password)
            );

            // Building the rest client
            RestClient restClient = RestClient.builder(httphost)
                    .setHttpClientConfigCallback(hc -> hc
                            .setDefaultCredentialsProvider(credsProv)
                            .setSSLContext(sslContext)
                    )
                    .build();
            ObjectMapper mapper = JsonMapper.builder()
                    .addModule(new JavaTimeModule())
                    .build();
            ElasticsearchTransport transport = new RestClientTransport(restClient,
                    new JacksonJsonpMapper(mapper));
            ElasticsearchClient esClient = new ElasticsearchClient(transport);

            // Creating the indexes
            // createSimpleIndex(esClient, USERS);


            return esClient;
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        } catch (KeyManagementException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 禁用ssl验证
     *
     * @return
     * @throws NoSuchAlgorithmException
     * @throws KeyManagementException
     */
    public SSLContext createInsecureSSLContext() throws NoSuchAlgorithmException, KeyManagementException {
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[]{new X509TrustManager() {
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            }

            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }}, new SecureRandom());
        return sslContext;

    }

    private void createSimpleIndex(ElasticsearchClient esClient, String index) throws IOException {
        BooleanResponse indexRes = esClient.indices().exists(ex -> ex.index(index));
        if (!indexRes.value()) {
            esClient.indices().create(c -> c
                    .index(index));
        }
    }

    private void createIndexWithDateMapping(ElasticsearchClient esClient, String index) throws IOException {
        BooleanResponse indexRes = esClient.indices().exists(ex -> ex.index(index));
        if (!indexRes.value()) {
            esClient.indices().create(c -> c
                    .index(index)
                    .mappings(m -> m
                            .properties("createdAt", p -> p
                                    .date(d -> d))
                            .properties("updatedAt", p -> p
                                    .date(d -> d))));

        }
    }
}
  1. 创建通用的操作,让所有Reposity继承此类

    package org.example.es.service;

    import co.elastic.clients.elasticsearch.ElasticsearchClient;
    import co.elastic.clients.elasticsearch._types.Refresh;
    import co.elastic.clients.elasticsearch._types.SortOrder;
    import co.elastic.clients.elasticsearch._types.query_dsl.Query;
    import co.elastic.clients.elasticsearch.core.*;
    import co.elastic.clients.transport.endpoints.BooleanResponse;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.core.toolkit.reflect.GenericTypeUtils;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import lombok.extern.slf4j.Slf4j;
    import org.example.es.util.TableInfoParamUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

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

    /**

    • 业务实现类封装

    • @author lyl

    • @version v1.0

    • @since 2025/5/21
      */
      @Slf4j
      public class EsService<T> {
      public final ElasticsearchClient esClient;

      /**

      • 索引字段,自动获取T对象里@Table注解字段名
        */
        public String index;
        private Class<T> clazz;

      /**

      • 获取当前类对象
      • @return
        */
        // private Class<T> getClazz() {
        // return (Class<T>) GenericTypeUtils.resolveTypeArguments(getClass(), EsService.class)[0];
        // }
        public EsService(ElasticsearchClient esClient, Class<T> clazz) {
        this.esClient = esClient;
        // 获取当前类对应的数据库表名
        TableInfoParamUtil tableInfoParamUtil = new TableInfoParamUtil();
        //Class<T> clazz = getClazz();
        this.clazz = clazz;
        this.index = tableInfoParamUtil.getTableName(clazz);
        }

      /**

      • 列表
      • @param params 参数列表,key为数据库字段名
      • @return
        */
        public List<T> list(Map<String, Object> params) {
        Class<T> clazz = this.clazz;
        try {
        List<Query> conditions = TableInfoParamUtil.changeMapParam(params);
        Query query = new Query.Builder().bool(b -> b.should(conditions)).build();
        SearchResponse<T> response = esClient.search(s -> s
        .index(index)
        .query(query)
        , clazz);
        return response.hits().hits().stream()
        .map(hit -> hit.source())
        .collect(Collectors.toList());
        } catch (IOException e) {
        throw new RuntimeException(e);
        }
        }

      /**

      • 分页查询
      • @param params 查询参数
      • @param pageNum 当前页码
      • @param pageSize 每页条数
      • @param sortField 排序字段
      • @return
        */
        public IPage<T> page(Map<String, Object> params, int pageNum, int pageSize, String sortField) {
        int offset = (pageNum - 1) * pageSize;
        List<Query> conditions = TableInfoParamUtil.changeMapParam(params);
        Query query = new Query.Builder().bool(b -> b.should(conditions)).build();
        try {
        SearchResponse<T> getArticle = esClient.search(ss -> ss
        .index(index)
        .size(pageSize) // how many results to return
        .from(offset) // starting point
        .query(query)
        .sort(srt -> srt
        .field(fld -> fld
        .field(sortField)
        .order(SortOrder.Desc))) // last updated first
        , this.clazz);
        if (getArticle.hits().hits().size() > 0) {
        List<T> collect = getArticle.hits().hits().stream()
        .map(hit -> hit.source())
        .collect(Collectors.toList());
        IPage<T> page = new Page<>(pageNum, pageSize);
        page.setRecords(collect);
        page.setTotal(getArticle.hits().total().value());
        return page;
        }
        } catch (IOException e) {
        log.error(e.getMessage());
        }
        return null;
        }

      /**

      • 保存
      • @param nodeDocument
        */
        public void save(T nodeDocument) {
        IndexRequest<T> articleReq = IndexRequest.of((id -> id
        .index(index)
        .id(TableInfoParamUtil.getIdValue(nodeDocument))
        .refresh(Refresh.WaitFor)
        .document(nodeDocument)));
        try {
        esClient.index(articleReq).id();
        } catch (IOException e) {
        log.error(e.getMessage());
        }

      }

      /**

      • 批量保存
      • @param nodeList
        */
        public boolean saveBatch(List<T> nodeList) {
        try {
        BulkRequest.Builder brBuilder = new BulkRequest.Builder();
        for (T product : nodeList) {
        brBuilder.operations(b -> b
        .index(idx -> idx.index(index)
        .id(TableInfoParamUtil.getIdValue(product))
        .document(product)));
        }
        BulkRequest bulkRequest = brBuilder.build();
        BulkResponse response = esClient.bulk(bulkRequest);
        if (!response.errors()) {
        return true;
        }
        } catch (IOException e) {
        log.error(e.getMessage());
        }
        return false;
        }

      /**

      • 根据id删除数据
      • @param id
        */
        public void delete(String id) {
        try {
        DeleteResponse deleteArticle = esClient.delete(d -> d
        .index(index)
        .id(id));
        if (!deleteArticle.result().name().equals("deleted")) {
        throw new RuntimeException("Failed to delete article");
        }
        } catch (IOException e) {
        throw new RuntimeException(e);
        }
        }

      /**

      • 更新
      • @param nodeDocument
        */
        public void update(T nodeDocument, String id) {
        try {
        Class<T> cla = (Class<T>) nodeDocument.getClass();
        boolean exists = esClient.exists(b -> b
        .index(index)
        .id(id)
        ).value();
        if (exists) {
        System.out.println("exists");
        UpdateResponse<T> upArticle = esClient.update(up -> up
        .index(index)
        .id(id)
        .doc(nodeDocument), cla);
        if (!upArticle.result().name().toLowerCase().equals("updated")) {
        throw new RuntimeException("Article update failed");
        }
        }
        } catch (IOException e) {
        throw new RuntimeException(e);
        }
        }

      /**

      • 根据id查询
      • @param id 对象id
      • @return
        */
        public T getById(String id) {
        try {
        GetResponse<T> searchResponse = esClient.get(ss -> ss
        .index(index)
        .id(id)
        , this.clazz);
        if (searchResponse.source() == null) {
        return null;
        }
        return searchResponse.source();
        } catch (IOException e) {
        throw new RuntimeException(e);
        }
        }

      /**

      • 获取总数
      • @return
        */
        public long getCount(Map<String, Object> params) {
        try {
        SearchResponse<T> searchResponse = null;
        if (null != params && params.size() > 0) {
        List<Query> conditions = TableInfoParamUtil.changeMapParam(params);
        Query query = new Query.Builder().bool(b -> b.should(conditions)).build();
        searchResponse = esClient.search(ss -> ss
        .index(index)
        .size(0)
        .query(query)
        , this.clazz);
        } else {
        //查全部
        searchResponse = esClient.search(ss -> ss
        .index(index)
        .size(0)
        .query(q -> q
        .matchAll(m -> m))
        , this.clazz);
        }
        return searchResponse.hits().total().value();
        } catch (IOException e) {
        throw new RuntimeException(e);
        }

      }

      public void createIndex(){
      try {
      BooleanResponse indexRes = esClient.indices().exists(ex -> ex.index(index));
      if (!indexRes.value()) {
      esClient.indices().create(c -> c
      .index(index));
      }

      复制代码
       } catch (IOException e) {
           throw new RuntimeException(e);
       }

      }

      public void deleteIndex(){
      try {
      BooleanResponse indexRes = esClient.indices().exists(ex -> ex.index(index));
      if (indexRes.value()) {
      esClient.indices().delete(d -> d
      .index(index));
      }
      } catch (IOException e) {
      throw new RuntimeException(e);
      }
      }

      public void createMapping() {
      try {
      BooleanResponse indexRes = esClient.indices().exists(ex -> ex.index(index));
      if (indexRes.value()) {
      esClient.indices().delete(d->d.index(index));
      esClient.indices().create(p -> p
      .index(index)
      .mappings(m -> m
      .properties("createDate", pp -> pp
      .date(d -> d.format("yyyy-MM-dd")))
      .properties("updateDate",pp->pp.date(d->d.format("yyyy-MM-dd HH:mm:ss")))));
      }
      } catch (IOException e) {
      throw new RuntimeException(e);
      }
      }

    }

从实体对象分析es的索引和id自动注入。

复制代码
package org.example.es.util;

import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import jakarta.persistence.Id;
import jakarta.persistence.Table;


import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 参数转换工具类
 *
 * @author lyl
 * @version v1.0
 * @since 2025/5/21
 */
public class TableInfoParamUtil {

    /**
     * 查询参数封装
     * @param params
     * @return
     */
    public static List<Query> changeMapParam(Map<String, Object> params) {
        List<Query> conditions = new ArrayList<>();

        params.forEach((k, v) -> {
            Query query = null;
            if (v instanceof String) {
                String value = v.toString();
                query = new MatchQuery.Builder()
                        .field(k)
                        .query(value).build()._toQuery();
            } else if (v instanceof Number) {
                query = new MatchQuery.Builder()
                        .field(k)
                        .query(Long.valueOf(v.toString())).build()._toQuery();
            } else if (v instanceof Boolean) {
                query = new MatchQuery.Builder()
                        .field(k)
                        .query(Boolean.valueOf(v.toString())).build()._toQuery();
            } else if (v instanceof Integer) {
                query = new MatchQuery.Builder()
                        .field(k)
                        .query(Integer.valueOf(v.toString())).build()._toQuery();
            } else if (v instanceof Float) {
                query = new MatchQuery.Builder()
                        .field(k)
                        .query(Float.valueOf(v.toString())).build()._toQuery();
            } else if (v instanceof Double) {
                query = new MatchQuery.Builder()
                        .field(k)
                        .query(Double.valueOf(v.toString())).build()._toQuery();
            }

            if (null != query) {
                conditions.add(query);
            }

        });
        return conditions;
    }


    /**
     * 获取表名,分析@Table注解
     * @param clazz
     * @return
     */
    public String getTableName(Class<?> clazz) {

        // 检查类是否有 Table 注解
        if (clazz.isAnnotationPresent(Table.class)) {
            // 获取注解实例
            Table tableAnnotation = clazz.getAnnotation(Table.class);
            // 读取注解的属性值
            String tableName = tableAnnotation.name();
            return tableName;
        }
        return null;
    }

    /**
     * 获取主键值
     * @param entity
     * @return
     */
    public static <T> String getIdValue(T entity) {
        Class<?> clazz = entity.getClass();

        for (Field field : clazz.getDeclaredFields()) {
            if (field.isAnnotationPresent(Id.class)) {
                field.setAccessible(true); // 允许访问私有字段
                try {
                    String id=String.valueOf(field.get(entity));
                    return id;   // 获取字段值
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return null;

    }
}

业务类实现EsService接口,除了基本增加,修改,删除,查询外,编写自定义查询方法。

复制代码
package org.example.es.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.Time;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeRelation;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import org.example.es.entity.StockBaseEntity;
import org.example.es.util.TableInfoParamUtil;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 业务实现类
 *
 * @author lyl
 * @version v1.0
 * @since 2025/5/26
 */
@Service
public class StockBaseResposity extends EsService<StockBaseEntity> {
    /**
     * 获取当前类对象
     *
     * @param esClient
     * @param clazz
     * @return
     */
    @Autowired
    public StockBaseResposity(ElasticsearchClient esClient) {
        super(esClient, StockBaseEntity.class);
    }


    /**
     * 把某一年的数据按7天分组
     * @return
     */

    public List<StockBaseEntity> search(){
        try {


            Query byMaxPrice = RangeQuery.of(r -> r
                    .date(n -> n
                            .field("createDate")
                            .gte("2024-09-01")
                            .lte("2025-04-01")
                            .format("yyyy-MM-dd"))
            )._toQuery();

            Query dateDistanceFeatureQuery = Query.of(q -> q.bool(b -> b
                    .must(m -> m.matchAll(mm -> mm))
                    .should(sh -> sh.distanceFeature(df -> df
                            .date(d -> d
                                    .field("createDate")
                                    .pivot(Time.of(t -> t.time("7d")))
                                    .origin("now"))))));
            //根据条件查询相应的数据
            Query query1 = Query.of(q->q.bool(b->b.must(byMaxPrice)));
            SearchResponse<StockBaseEntity> response = esClient.search(s -> s
                            .index(index)
                            .query(dateDistanceFeatureQuery)
                            .aggregations("dd",t->t.aggregations("day",
                                    a->a.dateHistogram(d->d.field("createDate").format("yyyy"))))
                            .size(100)
                    , StockBaseEntity.class);
            return response.hits().hits().stream()
                    .map(hit -> hit.source())
                    .collect(Collectors.toList());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }



}

三、高级查询

1. 使用

// 聚合查询示例

复制代码
   public List<StockBaseEntity> search(){
        try {
 
            Query dateDistanceFeatureQuery = Query.of(q -> q.bool(b -> b
                    .must(m -> m.matchAll(mm -> mm))
                    .should(sh -> sh.distanceFeature(df -> df
                            .date(d -> d
                                    .field("createDate")
                                    .pivot(Time.of(t -> t.time("7d")))
                                    .origin("now"))))));
            //查某一年的数据
            SearchResponse<StockBaseEntity> response = esClient.search(s -> s
                            .index(index)
                            .query(dateDistanceFeatureQuery)
                            .aggregations("dd",t->t.aggregations("day",
                                    a->a.dateHistogram(d->d.field("createDate").format("yyyy"))))
                            .size(100)
                    , StockBaseEntity.class);
            return response.hits().hits().stream()
                    .map(hit -> hit.source())
                    .collect(Collectors.toList());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

四、索引管理

1. 创建索引

复制代码
  public void createIndex(){
        try {
            BooleanResponse indexRes = esClient.indices().exists(ex -> ex.index(index));
            if (!indexRes.value()) {
                esClient.indices().create(c -> c
                        .index(index));
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

2. 索引映射管理

可以在实体类中使用注解定义更详细的映射:

Elasticsearch 默认支持的日期格式是 strict_date_optional_time,它支持以下形式的日期时间字符串:

yyyy-MM-dd

yyyy-MM-dd HH:mm:ss

yyyy-MM-dd HH:mm:ss.SSS

例如:

2023-10-01

2023-10-01 12:30:45

2023-10-01 12:30:45.123

如果你需要使用其他日期格式,可以在字段映射(mapping)中自定义日期格式。例如:

复制代码
    public void createMapping() {
        try {
            BooleanResponse indexRes = esClient.indices().exists(ex -> ex.index(index));
            if (indexRes.value()) {
//                esClient.indices().delete(d->d.index(index));
                esClient.indices().create(p -> p
                        .index(index)
                        .mappings(m -> m
                                .properties("createDate", pp -> pp
                                        .date(d -> d.format("yyyy-MM-dd")))
                                .properties("updateDate",pp->pp.date(d->d.format("yyyy-MM-dd HH:mm:ss")))));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

然后在resources目录下创建对应的settings和mappings文件。

五、性能优化

1. 批量操作:使用`bulk`API进行批量索引

复制代码
    /**
     * 批量保存
     *
     * @param nodeList
     */
    public boolean saveBatch(List<T> nodeList) {
        try {
            BulkRequest.Builder brBuilder = new BulkRequest.Builder();
            for (T product : nodeList) {
                brBuilder.operations(b -> b
                        .index(idx -> idx.index(index)
                                .id(TableInfoParamUtil.getIdValue(product))
                                .document(product)));
            }
            BulkRequest bulkRequest = brBuilder.build();
            BulkResponse response = esClient.bulk(bulkRequest);
            if (!response.errors()) {
                return true;
            }
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        return false;
    }

七、总结

Spring Boot与Elasticsearch的集成为开发强大的搜索功能提供了便利。通过elasticsearch-rest-client,我们可以:

  1. 使用熟悉的Repository模式进行基本操作

  2. 利用ElasticsearchTemplate实现复杂查询

  3. 轻松管理索引和映射

  4. 实现全文搜索、聚合分析等高级功能

在实际项目中,应根据业务需求合理设计索引结构,优化查询性能,并注意数据同步策略,确保Elasticsearch中的数据与主数据源保持一致。

通过本文介绍的方法,您可以快速在Spring Boot项目中集成Elasticsearch,构建高效的搜索和分析功能。

相关推荐
用户8307196840822 小时前
秒杀面试!MyBatis-Spring-Boot 初始化流程深度拆解
spring boot·mybatis
阿里巴巴P8高级架构师2 小时前
从0到1:用 Spring Boot 4 + Java 21 打造一个智能AI面试官平台
java·后端
stevenzqzq2 小时前
trace和Get thread dump的区别
java·android studio·断点
桦说编程2 小时前
并发编程踩坑实录:这些原则,帮你少走80%的弯路
java·后端·性能优化
程序猿零零漆2 小时前
Spring之旅 - 记录学习 Spring 框架的过程和经验(十三)SpringMVC快速入门、请求处理
java·学习·spring
BHXDML2 小时前
JVM 深度理解 —— 程序的底层运行逻辑
java·开发语言·jvm
用户8307196840822 小时前
Shiro登录验证与鉴权核心流程详解
spring boot·后端
tkevinjd2 小时前
net1(Java中的网络编程、TCP的三次握手与四次挥手)
java
码头整点薯条2 小时前
基于Java实现的简易规则引擎(日常开发难点记录)
java·后端
Elasticsearch2 小时前
使用 Elastic Agent 混合摄取加速 Otel 采用
elasticsearch