企业级开发SpringBoost玩转Elasticsearch

案例

Spring Boot 提供了 spring-data-elasticsearch 模块,可以方便地集成 Elasticsearch。 下面我们将详细讲解如何在 Spring Boot 中使用 Elasticsearch 8,并提供示例代码。

1. 添加依赖:

首先,需要在 pom.xml 文件中添加 spring-data-elasticsearch 的依赖。 注意,你需要选择与你的 Spring Boot 版本和 Elasticsearch 版本兼容的 spring-data-elasticsearch 版本

<dependencies>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-elasticsearch</artifactId>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-web</artifactId>

</dependency>

<!-- 其他依赖 -->

</dependencies>

2. 配置 Elasticsearch:

application.propertiesapplication.yml 文件中配置 Elasticsearch 的连接信息。

spring.elasticsearch.uris=http://localhost:9200

如果 Elasticsearch 开启了安全认证,需要配置用户名和密码

#spring.elasticsearch.username=elastic

#spring.elasticsearch.password=your_password

3. 创建实体类:

创建一个实体类,用于映射 Elasticsearch 中的文档结构。 使用 @Document 注解指定索引名称,使用 @Id 注解指定文档 ID 字段。

java 复制代码
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;


@Document(indexName = "products")  // 指定索引名称
public class Product {

    @Id  // 指定文档 ID 字段
    private String id;

    @Field(type = FieldType.Text, name = "name")
    private String name;

    @Field(type = FieldType.Double, name = "price")
    private Double price;

    // 必须要有默认构造函数
    public Product() {}

    public Product(String id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }
}

4. 创建 Repository 接口:

创建一个 Repository 接口,用于操作 Elasticsearch 中的文档。 继承 ElasticsearchRepository 接口,并指定实体类和 ID 的类型。

java 复制代码
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends ElasticsearchRepository<Product, String> {

    // 可以自定义查询方法,例如根据名称查询商品
    // Spring Data Elasticsearch 会根据方法名自动生成查询语句
    Iterable<Product> findByName(String name);
}

5. 创建 Service 类:

创建一个 Service 类,用于调用 Repository 接口,实现业务逻辑。

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    // 创建索引(可选,通常 Elasticsearch 会自动创建)
    public void createIndex() {
        // 注意: Spring Data Elasticsearch 提供了自动索引管理的机制,通常不需要手动创建索引
        // 如果需要自定义索引设置,可以使用 ElasticsearchClient 手动创建
        // 例如设置 Settings 和 Mappings。
    }


    // 添加商品
    public Product addProduct(Product product) {
        return productRepository.save(product);
    }

    // 根据ID查找商品
    public Product findProductById(String id) {
        return productRepository.findById(id).orElse(null);
    }

    // 根据名称查找商品
    public Iterable<Product> findProductsByName(String name) {
        return productRepository.findByName(name);
    }

    // 更新商品
    public Product updateProduct(Product product) {
        return productRepository.save(product); // save 方法在 ID 存在时更新文档
    }

    // 删除商品
    public void deleteProductById(String id) {
        productRepository.deleteById(id);
    }

    // 列出所有商品
    public Iterable<Product> getAllProducts() {
        return productRepository.findAll();
    }
}

6. 创建 Controller 类:

创建一个 Controller 类,提供 RESTful API 接口。

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@RestController
@RequestMapping("/products")
public class ProductController {

    @Autowired
    private ProductService productService;

    @PostMapping
    public Product addProduct(@RequestBody Product product) {
        return productService.addProduct(product);
    }

    @GetMapping("/{id}")
    public Product findProductById(@PathVariable String id) {
        return productService.findProductById(id);
    }

    @GetMapping("/name/{name}")
    public Iterable<Product> findProductsByName(@PathVariable String name) {
        return productService.findProductsByName(name);
    }

    @PutMapping
    public Product updateProduct(@RequestBody Product product) {
        return productService.updateProduct(product);
    }

    @DeleteMapping("/{id}")
    public void deleteProductById(@PathVariable String id) {
        productService.deleteProductById(id);
    }

    @GetMapping
    public Iterable<Product> getAllProducts() {
        return productService.getAllProducts();
    }
}

7. 完整示例与注意事项:

  • 自动索引管理: spring-data-elasticsearch 提供了自动索引管理的机制。 当你的应用程序启动时,它会自动检查 Elasticsearch 中是否存在与你的实体类对应的索引。 如果不存在,它会自动创建索引,并根据实体类的注解设置 mappings。 如果需要更细粒度的控制,你可以禁用自动索引管理,并使用 ElasticsearchClient 手动创建索引。

  • ElasticsearchClient 的使用 (高级): spring-data-elasticsearch 默认提供了 ElasticsearchRestTemplate,可以方便地执行 Elasticsearch 操作。 如果你需要更高级的功能,例如手动创建索引、执行复杂的查询等,可以使用 ElasticsearchClient

java 复制代码
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.Map;

@Service
public class ElasticsearchIndexService {

    @Autowired
    private ElasticsearchOperations  elasticsearchOperations;

    public void createIndex(String indexName, Map<String, Object> mappings) throws IOException {
        ElasticsearchClient client = elasticsearchOperations.getClient();
        CreateIndexRequest request = new CreateIndexRequest.Builder()
                .index(indexName)
                .mappings(m -> m.properties(mappings)) //  这里 mappings 是一个 Map<String,Object>
                .build();

        CreateIndexResponse response = client.indices().create(request);

        if (response.acknowledged()) {
            System.out.println("Index '" + indexName + "' created successfully.");
        } else {
            System.out.println("Failed to create index '" + indexName + "'.");
        }
    }
}

企业中使用

**ElasticsearchClient**是 Elasticsearch Java 客户端库中的核心组件,用于与 Elasticsearch 集群进行交互。它提供了一系列方法来执行各种操作,如索引文档、搜索数据、更新文档、删除文档等。

java 复制代码
//根据查询条件删除    
@Override
    public DeleteByQueryResponse delete(@NonNull Class<?> model, @NonNull Query query) {
        ModelContext context = ModelContext.of(model);
        DeleteByQueryRequest request = new DeleteByQueryRequest.Builder().index(context.getIndex()).query(query)
                .conflicts(Conflicts.Proceed).build();
        try {
            return this.client.deleteByQuery(request);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

Query query = QueryBuilders.term(t -> t.field("resumeId").value(resume.getId()));
DeleteByQueryResponse response = this.elasticsearchHandler.delete(Vitae.class, query);

//使用
DeleteRequest request = new DeleteRequest.Builder().index(context.getIndex()).id(id).build();

前期学习

修改文档:

全量修改:PUT/索引库名/_doc/文档id{json文档}

增量修改:POST/索引库名/_update/文档id{"doc":{字段}}

java 复制代码
    @Override
    public UpdateByQueryResponse update(@NonNull Class<?> model, @NonNull Query query,
                                        @NonNull Map<String, ?> parameters) {
        ModelContext context = ModelContext.of(model);
        StringBuilder source = new StringBuilder();
        Map<String, JsonData> params = Maps.newHashMapWithExpectedSize(parameters.size());
        parameters.forEach((key, value) -> {
            if (source.length() > 0) {
                source.append(";");
            }
            if (value == null) {
                source.append("ctx._source.").append(key).append("=null");
            } else {
                source.append("ctx._source.").append(key).append("=params.").append(key);
                params.put(key, JsonData.of(value));
            }
        });
        Script script = new Script.Builder().lang(ScriptLanguage.Painless).source(source.toString())
                .params(params).build();
        UpdateByQueryRequest request = new UpdateByQueryRequest.Builder().index(context.getIndex()).script(script)
                .query(query).conflicts(Conflicts.Proceed).build();
        try {
            return this.client.updateByQuery(request);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

//使用
UpdateByQueryResponse response = this.elasticsearchHandler.update(Vitae.class, query, parameters);
java 复制代码
    @Override
    public UpdateByQueryResponse update(@NonNull Class<?> model, @NonNull Query query, @NonNull String field,
                                        Object value) {
        ModelContext context = ModelContext.of(model);
        String source = "ctx._source." + field + (value == null ? "=null" : ("=params." + field));
        Map<String, JsonData> params = value == null ? Collections.emptyMap() :
                ImmutableMap.of(field, JsonData.of(value));
        Script script = new Script.Builder().lang(ScriptLanguage.Painless).source(source).params(params).build();
        UpdateByQueryRequest request = new UpdateByQueryRequest.Builder().index(context.getIndex()).script(script)
                .query(query).conflicts(Conflicts.Proceed).build();
        try {
            return this.client.updateByQuery(request);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


//使用
this.elasticsearchHandler.update(Vitae.class, query, "topping", document);
java 复制代码
 @Override
    public <T> UpdateResponse<T> update(@NonNull Class<T> model, @NonNull String id, @NonNull Object document,
                                        Long primary, Long sequence) {
        ModelContext context = ModelContext.of(model);
        UpdateRequest<T, ?> request = new UpdateRequest.Builder<T, Object>().index(context.getIndex()).id(id)
                .doc(document).ifPrimaryTerm(primary).ifSeqNo(sequence).retryOnConflict(3).build();
        try {
            return this.client.update(request, model);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


//使用
this.elasticsearchHandler.update(Job.class, id, ImmutableMap.of("recruiting", recruiting));

前期学习

springboot操作es的DSL

QueryBuilders 常用方法
全文查询
  • matchQuery(String field, Object value): 全文匹配

  • multiMatchQuery(Object value, String... fields): 多字段匹配

  • matchPhraseQuery(String field, Object value): 短语匹配

  • matchPhrasePrefixQuery(String field, Object value): 短语前缀匹配

词项查询
  • termQuery(String field, Object value): 精确词项匹配

  • termsQuery(String field, String... values): 多词项精确匹配

  • rangeQuery(String field): 范围查询(如数值、日期)

  • existsQuery(String field): 检查字段是否存在

  • prefixQuery(String field, String value): 前缀匹配

  • wildcardQuery(String field, String value): 通配符匹配(如*?

  • regexpQuery(String field, String value): 正则表达式匹配

  • fuzzyQuery(String field, String value): 模糊匹配(容错匹配)

复合查询
  • boolQuery(): 布尔查询(组合多个子查询)

  • disMaxQuery(): 取多个查询中的最佳匹配

  • constantScoreQuery(QueryBuilder query): 固定评分查询

  • nestedQuery(String path, QueryBuilder query, ScoreMode scoreMode): 嵌套对象查询

  • functionScoreQuery(QueryBuilder query, FunctionScoreBuilder... functions): 自定义评分函数

地理位置查询

  • geoDistanceQuery(String field): 地理距离范围查询

  • geoBoundingBoxQuery(String field): 地理边界框查询

其他查询
  • scriptQuery(Script script): 脚本查询

  • idsQuery().addIds(String... ids): 根据ID查询


综合示例一

假设有一个products索引,包含以下字段:

  • name(文本)

  • price(整数)

  • status(关键字)

  • tags(关键字数组)

  • location(地理坐标)

  • metadata(嵌套类型,包含keyvalue

java 复制代码
import org.elasticsearch.index.query.*;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.script.Script;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.action.search.SearchRequest;

public class QueryExample {
    public static void main(String[] args) {
        // 1. 布尔查询(组合多个子查询)
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();

        // 2. 全文匹配:搜索name中包含"phone"
        boolQuery.must(QueryBuilders.matchQuery("name", "phone"));

        // 3. 多字段匹配:在name和description中搜索"smart"
        boolQuery.must(QueryBuilders.multiMatchQuery("smart", "name", "description"));

        // 4. 短语匹配:description中精确匹配"high quality"
        boolQuery.must(QueryBuilders.matchPhraseQuery("description", "high quality"));

        // 5. 精确词项匹配:status为"active"
        boolQuery.must(QueryBuilders.termQuery("status", "active"));

        // 6. 多词项匹配:tags包含"electronics"或"gadgets"
        boolQuery.must(QueryBuilders.termsQuery("tags", "electronics", "gadgets"));

        // 7. 范围查询:price在100到500之间
        boolQuery.must(QueryBuilders.rangeQuery("price").gte(100).lte(500));

        // 8. 存在性检查:stock字段必须存在
        boolQuery.filter(QueryBuilders.existsQuery("stock"));

        // 9. 前缀匹配:description以"latest"开头
        boolQuery.should(QueryBuilders.prefixQuery("description", "latest"));

        // 10. 通配符匹配:tags字段匹配"tec*"(如"tech")
        boolQuery.should(QueryBuilders.wildcardQuery("tags", "tec*"));

        // 11. 正则表达式匹配:name匹配正则"pho.*e"
        boolQuery.should(QueryBuilders.regexpQuery("name", "pho.*e"));

        // 12. 模糊查询:name容错匹配"fone"
        boolQuery.should(QueryBuilders.fuzzyQuery("name", "fone"));

        // 13. 地理距离查询:location距离(37.7749, -122.4194)10公里内
        boolQuery.filter(QueryBuilders.geoDistanceQuery("location")
                .point(37.7749, -122.4194)
                .distance("10km"));

        // 14. 嵌套查询:metadata.key为"color"且metadata.value为"black"
        boolQuery.must(QueryBuilders.nestedQuery("metadata",
                QueryBuilders.boolQuery()
                        .must(QueryBuilders.termQuery("metadata.key", "color"))
                        .must(QueryBuilders.matchQuery("metadata.value", "black")),
                ScoreMode.Total));

        // 15. 脚本查询:price大于200
        Script script = new Script("doc['price'].value > 200");
        boolQuery.filter(QueryBuilders.scriptQuery(script));

        // 构建搜索请求
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(boolQuery);
        SearchRequest searchRequest = new SearchRequest("products");
        searchRequest.source(sourceBuilder);

        // 执行搜索(需Elasticsearch客户端)
        // SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
    }
}
综合案例二
java 复制代码
 @Override
    public List<Job> listSearchJobs(@NonNull TextSearch search) {
        List<Query> pairs = Lists.newLinkedList();
        if (search.getRegion() != null) {
            pairs.add(this.jobRegionQuery("regionId", search.getRegion()));
        }
        if (StringUtils.notEmpty(search.getKeyword())) {
            pairs.add(QueryBuilders.match(b -> b.field("description").query(search.getKeyword())));
        }
        if (search.getType() != null) {
            // 指定职位类型
            pairs.add(QueryBuilders.term(t -> t.field("type").value(search.getType().name())));
        }
        if (ObjectUtils.notEmpty(search.getDriveLicenses())) {
            List<FieldValue> values = search.getDriveLicenses().stream()
                    .map(driveLicense -> FieldValue.of(driveLicense.name())).collect(Collectors.toList());
            pairs.add(QueryBuilders.terms(t -> t.field("driveLicense").terms(v -> v.value(values))));
        }
        if (ObjectUtils.isEmpty(pairs)) {
            return Collections.emptyList();
        }
        pairs.add(QueryBuilders.term(t -> t.field("status").value(ContentStatus.ONLINE.name())));
        Query query = QueryBuilders.bool(b -> b.must(pairs));
        SearchResponse<Job> response = this.elasticsearchHandler.search(Job.class, query,
                b -> b.sort(s -> s.field(f -> f.field("refreshTime").order(SortOrder.Desc)))
                        .sort(s -> s.field(f -> f.field("_score").order(SortOrder.Desc)))
                        .from(search.getPaging().getIndex()).size(search.getPaging().getSize()));
        return ElasticsearchUtils.sources(response);
    }

根据id查询:

java 复制代码
    @Override
    public <T> MgetResponse<T> get(@NonNull Class<T> model, @NonNull List<String> ids) {
        ModelContext context = ModelContext.of(model);
        MgetRequest request = new MgetRequest.Builder().index(context.getIndex()).ids(ids).build();
        try {
            return this.client.mget(request, model);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

//使用
MgetResponse<Vitae> response = this.elasticsearchHandler.get(Vitae.class, vitaeIds);
Set<Long> exists = response.docs().stream().map(doc -> doc.result().source()).filter(Objects::nonNull)
                    .map(Vitae::getId).collect(Collectors.toSet());

滚动:

java 复制代码
    @Override
    public <T> ScrollResponse<T> scroll(@NonNull Class<T> model, @NonNull String id, @NonNull String time) {
        ScrollRequest request = new ScrollRequest.Builder().scrollId(id).scroll(s -> s.time(time)).build();
        try {
            return this.client.scroll(request, model);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

//使用
scrollResponse = elasticsearchHandler.scroll(Job.class, scrollId, keyLive + "m");
ElasticsearchUtils.sources(scrollResponse)
相关推荐
柯南二号1 小时前
【Java后端】Spring Boot 集成 MyBatis-Plus 全攻略
java·spring boot·mybatis
javachen__2 小时前
SpringBoot整合P6Spy实现全链路SQL监控
spring boot·后端·sql
一只栖枝6 小时前
华为 HCIE 大数据认证中 Linux 命令行的运用及价值
大数据·linux·运维·华为·华为认证·hcie·it
IT毕设实战小研8 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
一只爱撸猫的程序猿9 小时前
使用Spring AI配合MCP(Model Context Protocol)构建一个"智能代码审查助手"
spring boot·aigc·ai编程
甄超锋9 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
喂完待续10 小时前
Apache Hudi:数据湖的实时革命
大数据·数据仓库·分布式·架构·apache·数据库架构
青云交10 小时前
Java 大视界 -- 基于 Java 的大数据可视化在城市交通拥堵治理与出行效率提升中的应用(398)
java·大数据·flink·大数据可视化·拥堵预测·城市交通治理·实时热力图
武昌库里写JAVA12 小时前
JAVA面试汇总(四)JVM(一)
java·vue.js·spring boot·sql·学习
Pitayafruit13 小时前
Spring AI 进阶之路03:集成RAG构建高效知识库
spring boot·后端·llm