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 则为我们提供了更灵活的底层控制能力,非常适合用于构建企业级搜索功能。


参考资料

相关推荐
思想在飞肢体在追36 分钟前
Springboot项目配置Nacos
java·spring boot·后端·nacos
JavaGuide3 小时前
推荐一个基于 Spring Boot 4.0 + Java 21 + Spring AI 2.0 的大模型项目!
java·spring boot·spring
小马爱打代码3 小时前
Spring Boot :使用 Spring Cache 注解方式集成 Redis
spring boot·redis·spring
wdfk_prog3 小时前
解决 `git cherry-pick` 引入大量新文件的问题
大数据·git·elasticsearch
东东5164 小时前
果园预售系统的设计与实现spingboot+vue
前端·javascript·vue.js·spring boot·个人开发
洛阳纸贵4 小时前
JAVA高级工程师--Elasticsearch
大数据·elasticsearch·搜索引擎
TracyCoder1234 小时前
ElasticSearch内存管理与操作系统(二):深入解析 Circuit Breakers(熔断器)机制
大数据·elasticsearch·搜索引擎
不光头强4 小时前
spring boot项目欢迎页设置方式
java·spring boot·后端
怪兽毕设4 小时前
基于SpringBoot的选课调查系统
java·vue.js·spring boot·后端·node.js·选课调查系统
学IT的周星星5 小时前
Spring Boot Web 开发实战:第二天,从零搭个“会卖萌”的小项目
spring boot·后端·tomcat