Elasticsearch 是一個基于 Lucene 的分布式搜索服務器,具有高效的全文檢索能力。在現代應用中,尤其是需要強大搜索功能的系統中,Elasticsearch 被廣泛使用。
Spring Boot 提供了對 Elasticsearch 的集成支持,使得開發者可以輕松地將 Elasticsearch 集成到 Spring Boot 應用中,實現高效的搜索、分析等功能。本文將詳細介紹如何在 Spring Boot 中集成 Elasticsearch,并展示一些基本的使用示例以及通過 ElasticsearchRestTemplate
實現的高級功能。
一、準備工作
1. 安裝 Elasticsearch
你可以通過以下方式安裝 Elasticsearch:
- 使用 Docker 安裝:
docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:8.11.3
- 直接下載安裝包:Elasticsearch 下載頁面
確保 Elasticsearch 成功啟動后,可以通過瀏覽器訪問 http://localhost:9200
查看其狀態信息。
二、創建 Spring Boot 項目
使用 Spring Initializr 創建一個 Spring Boot 項目,選擇以下依賴:
- Spring Web
- Spring Data Elasticsearch
或者手動在 pom.xml
中添加如下依賴(適用于 Maven 項目):
<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.yml
或 application.properties
中配置 Elasticsearch 連接信息:
spring:elasticsearch:rest:uris: http://localhost:9200
如果你使用的是較老版本的 Spring Boot,可能需要手動配置 RestHighLevelClient
,但在新版本中已默認使用 ElasticsearchRestTemplate
和 RestClient
。
四、定義實體類
我們先定義一個簡單的實體類,并使用注解來映射 Elasticsearch 索引結構。
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 {@Idprivate 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
接口,我們可以繼承它來操作文檔。
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;public interface BlogPostRepository extends ElasticsearchRepository<BlogPost, String> {
}
六、編寫 Service 層
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Optional;@Service
public class BlogPostService {@Autowiredprivate 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 層
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 {@Autowiredprivate BlogPostService blogPostService;@PostMappingpublic BlogPost create(@RequestBody BlogPost blogPost) {return blogPostService.save(blogPost);}@GetMapping("/{id}")public BlogPost get(@PathVariable String id) {return blogPostService.findById(id).orElse(null);}@GetMappingpublic 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
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
2. 構建基本查詢(Match 查詢)
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 構建復雜查詢
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)
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. 高亮顯示匹配內容
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. 分頁查詢
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
則為我們提供了更靈活的底層控制能力,非常適合用于構建企業級搜索功能。
參考資料
- Spring Data Elasticsearch 官方文檔
- Elasticsearch 官網
- IK Analyzer GitHub