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


参考资料

相关推荐
_码农121381 小时前
spring boot + mybatis + mysql 只有一个实体类的demo
spring boot·mysql·mybatis
c_zyer2 小时前
FreeSWITCH与Java交互实战:从EslEvent解析到Spring Boot生态整合的全指南
spring boot·netty·freeswitch·eslevent
郝学胜-神的一滴2 小时前
Spring Boot Actuator 保姆级教程
java·开发语言·spring boot·后端·程序人生
斜月3 小时前
Springboot 项目加解密的那些事儿
spring boot·后端
草莓爱芒果3 小时前
Spring Boot中使用Bouncy Castle实现SM2国密算法(与前端JS加密交互)
java·spring boot·算法
汤姆yu5 小时前
基于springboot的快递分拣管理系统
java·spring boot·后端
你知道烟火吗8 小时前
谈谈对反射的理解?
java·开发语言·spring boot·后端
it自8 小时前
Redisson在Spring Boot项目中的集成与实战
java·spring boot·redis·后端·缓存
我命由我1234510 小时前
Spring Boot 项目问题:Web server failed to start. Port 5566 was already in use.
java·前端·jvm·spring boot·后端·spring·java-ee