Spring Boot 集成 Elasticsearch(含 ElasticsearchRestTemplate 示例)

Elasticsearch 是一个基于 Lucene 的分布式搜索服务器,具有高效的全文检索能力。在现代应用中,尤其是需要强大搜索功能的系统中,Elasticsearch 被广泛使用。

Spring Boot 提供了对 Elasticsearch 的集成支持,使得开发者可以轻松地将 Elasticsearch 集成到 Spring Boot 应用中,实现高效的搜索、分析等功能。本文将详细介绍如何在 Spring Boot 中集成 Elasticsearch,并展示一些基本的使用示例以及通过 ElasticsearchRestTemplate 实现的高级功能。

一、准备工作

1. 安装 Elasticsearch

你可以通过以下方式安装 Elasticsearch:

  • 使用 Docker 安装
bash 复制代码
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:8.11.3

确保 Elasticsearch 成功启动后,可以通过浏览器访问 http://localhost:9200 查看其状态信息。


二、创建 Spring Boot 项目

使用 Spring Initializr 创建一个 Spring Boot 项目,选择以下依赖:

  • Spring Web
  • Spring Data Elasticsearch

或者手动在 pom.xml 中添加如下依赖(适用于 Maven 项目):

xml 复制代码
<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- 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>
        <scope>provided</scope>
    </dependency>
</dependencies>

三、配置 Elasticsearch

application.ymlapplication.properties 中配置 Elasticsearch 连接信息:

yaml 复制代码
spring:
  elasticsearch:
    rest:
      uris: http://localhost:9200

如果你使用的是较老版本的 Spring Boot,可能需要手动配置 RestHighLevelClient,但在新版本中已默认使用 ElasticsearchRestTemplateRestClient


四、定义实体类

我们先定义一个简单的实体类,并使用注解来映射 Elasticsearch 索引结构。

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 = "blog-post", shards = 1)
public class BlogPost {

    @Id
    private String id;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String content;

    // 构造方法、getter/setter 省略
}

注意:如果使用中文分词,建议使用 ik-analyzer 插件,在 Elasticsearch 中安装后指定 analyzer


五、定义 Repository 接口

Spring Data 提供了 ElasticsearchRepository 接口,我们可以继承它来操作文档。

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

public interface BlogPostRepository extends ElasticsearchRepository<BlogPost, String> {
}

六、编写 Service 层

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

import java.util.List;
import java.util.Optional;

@Service
public class BlogPostService {

    @Autowired
    private BlogPostRepository blogPostRepository;

    public BlogPost save(BlogPost blogPost) {
        return blogPostRepository.save(blogPost);
    }

    public Optional<BlogPost> findById(String id) {
        return blogPostRepository.findById(id);
    }

    public Iterable<BlogPost> findAll() {
        return blogPostRepository.findAll();
    }

    public void deleteById(String id) {
        blogPostRepository.deleteById(id);
    }

    public List<BlogPost> searchByTitle(String title) {
        return blogPostRepository.findByTitle(title);
    }
}

七、编写 Controller 层

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

import java.util.List;

@RestController
@RequestMapping("/api/blog-posts")
public class BlogPostController {

    @Autowired
    private BlogPostService blogPostService;

    @PostMapping
    public BlogPost create(@RequestBody BlogPost blogPost) {
        return blogPostService.save(blogPost);
    }

    @GetMapping("/{id}")
    public BlogPost get(@PathVariable String id) {
        return blogPostService.findById(id).orElse(null);
    }

    @GetMapping
    public Iterable<BlogPost> getAll() {
        return blogPostService.findAll();
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable String id) {
        blogPostService.deleteById(id);
    }

    @GetMapping("/search")
    public List<BlogPost> search(@RequestParam String title) {
        return blogPostService.searchByTitle(title);
    }
}

八、补充内容:使用 ElasticsearchRestTemplate

除了使用 Repository,我们还可以通过 ElasticsearchRestTemplate 来执行更复杂的查询、聚合、高亮等操作。

1. 注入 ElasticsearchRestTemplate

java 复制代码
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;

2. 构建基本查询(Match 查询)

java 复制代码
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;

public List<BlogPost> searchByTitleWithTemplate(String keyword) {
    Criteria criteria = new Criteria("title").is(keyword);
    CriteriaQuery query = new CriteriaQuery(criteria);

    SearchHits<BlogPost> hits = elasticsearchRestTemplate.search(query, BlogPost.class);
    return hits.stream()
               .map(SearchHit::getContent)
               .collect(Collectors.toList());
}

3. 使用 NativeSearchQueryBuilder 构建复杂查询

java 复制代码
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.MatchQueryBuilder;

import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;

public List<BlogPost> searchByContentWithMatchQuery(String keyword) {
    MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("content", keyword);

    SearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(matchQueryBuilder)
            .build();

    SearchHits<BlogPost> hits = elasticsearchRestTemplate.search(searchQuery, BlogPost.class);
    return hits.stream()
               .map(SearchHit::getContent)
               .collect(Collectors.toList());
}

4. 聚合查询(Aggregation)

java 复制代码
import org.elasticsearch.index.aggregation.AggregationBuilders;
import org.elasticsearch.index.aggregation.bucket.terms.TermsAggregationBuilder;

public void aggregateKeywordsInTitle() {
    TermsAggregationBuilder aggregation = AggregationBuilders.terms("keywords")
            .field("title.keyword") // 注意字段类型应为 keyword
            .size(10);

    SearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withAggregations(aggregation)
            .build();

    SearchHits<BlogPost> hits = elasticsearchRestTemplate.search(searchQuery, BlogPost.class);

    Terms keywordsAgg = hits.getAggregations().get("keywords");
    for (Terms.Bucket entry : keywordsAgg.getBuckets()) {
        System.out.println(entry.getKey() + ":" + entry.getDocCount());
    }
}

5. 高亮显示匹配内容

java 复制代码
import org.elasticsearch.index.highlight.HighlightBuilder;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;

public List<BlogPost> searchWithHighlight(String keyword) {
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.field("content"); // 对 content 字段进行高亮
    highlightBuilder.preTags("<strong>").postTags("</strong>");

    MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("content", keyword);

    SearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(matchQuery)
            .withHighlightBuilder(highlightBuilder)
            .build();

    SearchHits<BlogPost> hits = elasticsearchRestTemplate.search(searchQuery, BlogPost.class);

    return hits.stream().map(hit -> {
        Map<String, List<String>> highlightFields = hit.getHighlightFields();
        if (highlightFields.containsKey("content")) {
            String highlightedContent = String.join("...", highlightFields.get("content"));
            hit.getContent().setContent(highlightedContent); // 替换内容为高亮版本
        }
        return hit.getContent();
    }).collect(Collectors.toList());
}

6. 分页查询

java 复制代码
public List<BlogPost> searchWithPagination(String keyword, int page, int size) {
    MatchQueryBuilder matchQuery = QueryBuilders.matchQuery("title", keyword);

    SearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(matchQuery)
            .withPageable(PageRequest.of(page, size))
            .build();

    SearchHits<BlogPost> hits = elasticsearchRestTemplate.search(searchQuery, BlogPost.class);
    return hits.stream().map(SearchHit::getContent).collect(Collectors.toList());
}

九、测试 API

你可以使用 Postman 或 curl 测试以下接口:

  • POST /api/blog-posts:创建一篇博客
  • GET /api/blog-posts/{id}:获取某篇博客
  • GET /api/blog-posts:获取所有博客
  • DELETE /api/blog-posts/{id}:删除博客
  • GET /api/blog-posts/search?title=xxx:按标题搜索博客
  • 更多通过 ElasticsearchRestTemplate 支持的高级查询也可以通过新增接口调用。

十、总结

本文详细介绍了如何在 Spring Boot 项目中集成 Elasticsearch,并实现了基本的增删改查操作。同时,通过 ElasticsearchRestTemplate 展示了构建复杂查询、聚合、高亮、分页等高级功能的方式。

通过 Spring Data Elasticsearch 提供的抽象,我们可以非常方便地进行数据持久化和检索。而 ElasticsearchRestTemplate 则为我们提供了更灵活的底层控制能力,非常适合用于构建企业级搜索功能。


参考资料

相关推荐
文艺倾年12 分钟前
【八股消消乐】Elasticsearch优化—检索Labubu
大数据·elasticsearch·搜索引擎
篱笆院的狗22 分钟前
Spring Boot 工程启动以后,我希望将数据库中已有的固定内容,打入到 Redis 缓存中,请问如何处理?
数据库·spring boot·缓存
会飞的小妖1 小时前
Elasticsearch相关操作
elasticsearch
昂子的博客1 小时前
Springboot仿抖音app开发之Nacos 分布式服务与配置中心(进阶)
java·spring boot·redis·后端·mysql·ip
还是鼠鼠2 小时前
JavaWeb RESTful 开发规范入门
java·数据库·spring boot·后端·spring·mybatis·restful
Elastic 中国社区官方博客3 小时前
ECK 简化:在 GCP GKE Autopilot 上部署 Elasticsearch
大数据·elasticsearch·搜索引擎·k8s·全文检索·googlecloud
风象南3 小时前
SpringBoot的4种数据水平分片策略
java·spring boot·后端
@ chen3 小时前
Spring Boot自动装配原理解析
java·spring boot·后端
Narutolxy3 小时前
Spring Boot 应用仅在 IPv6 可达?一次跨越系统、网络与配置的全流程排查实战20250616
网络·spring boot·后端
专注代码七年3 小时前
Spring Boot单元测试终极指南:从环境搭建到分层测试实战
java·spring boot·junit