1. 概述
前面我們聊了 Elasticsearch(ES)集群的搭建,今天我們來聊一下,Elasticsearch(ES)集群如何與 Springboot 進行整合。
Elasticsearch(ES)集群的搭建可參見我的另一篇文章《Elasticsearch(ES)集群的搭建》。
Elasticsearch(ES)集群 我們采用的是目前最新的 7.14.1 版本。
Springboot 我們采用的是目前最新的 2.5.4 版本。
2. Springboot 與 Elasticsearch(ES)的版本對應問題
從 spring-boot-starter-data-elasticsearch 的 jar 包依賴來看,最新的 Springboot 2.5.4 版本 對應的 ElasticSearch(ES)版本應該是 7.12.1。
經本人親測,API完全可以兼容 ElasticSearch(ES)7.14.1 版本,所以完全不用擔心兼容問題。
如果擔心有風險,可搭建 ElasticSearch(ES)7.12.1 版本的集群,搭建方法與 7.14.1 版本完全一致,可參見我的另一篇文章《Elasticsearch(ES)集群的搭建》。
ElasticSearch(ES)7.12.1 版本下載地址:
https://www.elastic.co/cn/downloads/past-releases#elasticsearch
https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7-12-1
3. Elasticsearch(ES )與 Springboot 的整合
3.1 使用 InterlliJ IDEA 創建Springboot項目
1)打開IDEA,選擇 New —> Project...
2)選擇 Spring Initializr, 填寫項目名稱等信息,點擊【Next】
3)依賴中勾選 Spring Data Elasticsearch(Access+Driver),點擊【Finish】即可
4)pom.xml 中的主要依賴
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.4</version> <relativePath/> <!-- lookup parent from repository --> </parent>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.2 在 Elasticsearch(ES) 中創建索引 index_user 同時創建映射
特別說明:
不建議使用 Java代碼 對索引進行管理類操作,例如:創建索引、更新映射、刪除索引。
類似在mysql,我們通常不會用 Java代碼 去進行建庫、建表、改表、刪表的操作,只用代碼對表中的數據進行增刪改查操作。
PUT http://192.168.1.8:9200/index_user
參數:
{ "settings":{ "index":{ "number_of_shards":3, "number_of_replicas":1 } }, "mappings" : { "properties":{ "user_id":{ "type":"keyword" }, "name":{ "type": "text", "fields": { "keyword": { "ignore_above": 256, "type": "keyword" } }, "analyzer":"ik_max_word" }, "login_name":{ "type":"keyword" }, "age":{ "type":"integer" }, "birthday":{ "type":"date" }, "desc":{ "type":"text", "analyzer":"ik_max_word" }, "head_url":{ "type":"text", "index":false } } } }
3.3 配置 Springboot 配置文件
打開 application.yml 文件,將 Elasticsearch(ES)的集群信息配置在里面
spring: data: elasticsearch: client: reactive: endpoints: 192.168.1.8:9200,192.168.1.22:9200,192.168.1.144:9200 elasticsearch: rest: uris: 192.168.1.8:9200,192.168.1.22:9200,192.168.1.144:9200
3.4 創建實體類 User
在實體類中用注解標識 實體 與 索引 的對應關系
import lombok.Builder; import lombok.Getter; import lombok.Setter; 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; import org.springframework.data.elasticsearch.annotations.Setting; import java.util.Date; @Builder @Setter @Getter @Setting(shards = 3, replicas = 1) @Document(indexName = "index_user", createIndex = false) public class User { @Id @Field(store = true, name = "user_id", type = FieldType.Keyword) private String userId; @Field(store = true, searchAnalyzer = "ik_max_word", analyzer = "ik_max_word") private String name; @Field(store = true, name = "login_name", type = FieldType.Keyword) private String loginName; @Field(store = true, type = FieldType.Integer) private Integer age; @Field(store = true, type = FieldType.Date) private Date birthday; @Field(store = true, searchAnalyzer = "ik_max_word", analyzer = "ik_max_word") private String desc; @Field(store = true, name = "head_url", type = FieldType.Keyword) private String headUrl; }
3.5 單條文檔的新增或更新
這里我們使用單元測試,演示一下,Java代碼是如何操作Elasticsearch(ES)的。
文檔ID不存在則新增文檔,ID存在則更新文檔
import cn.zhuifengren.myelasticsearch.pojo.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder; import java.text.ParseException; import java.text.SimpleDateFormat; @SpringBootTest(classes = MyelasticsearchApplication.class) public class ElasticsearchTest { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @Test public void save() throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); User user = User.builder() .userId("2") .name("夏維爾") .loginName("xwe") .age(28) .birthday(sdf.parse("1992-06-06")) .desc("我是一名高級開發經理,每天坐地鐵上班,在北京住,從不堵車") .headUrl("https://www.zhuifengren.cn/img/xwe.jpg") .build(); elasticsearchRestTemplate.save(user); } }
3.6 依據文檔ID更新文檔的部分字段
import cn.zhuifengren.myelasticsearch.pojo.User; import cn.zhuifengren.myelasticsearch.utils.JsonUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.core.query.UpdateResponse; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; @SpringBootTest(classes = MyelasticsearchApplication.class) public class ElasticsearchTest { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @Test public void update() { Map<String, Object> params = new HashMap<>(); params.put("name", "夏維爾5"); Document document = Document.from(params); UpdateQuery updateQuery = UpdateQuery.builder("2") // 2 是文檔的ID .withDocument(document) .build(); UpdateResponse result = elasticsearchRestTemplate.update(updateQuery, IndexCoordinates.of("index_user")); System.out.println(JsonUtils.objectToJson(result)); // 結果:{"result":"UPDATED"} } }
3.7 依據文檔ID獲得文檔
import cn.zhuifengren.myelasticsearch.pojo.User; import cn.zhuifengren.myelasticsearch.utils.JsonUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.core.query.UpdateResponse; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; @SpringBootTest(classes = MyelasticsearchApplication.class) public class ElasticsearchTest { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @Test public void getById() { User user = elasticsearchRestTemplate.get("2", User.class); System.out.println(JsonUtils.objectToJson(user)); // 結果:{"userId":"2","name":"夏維爾5","loginName":"xwe","age":28,"birthday":707760000000,"desc":"我是一名高級開發經理,每天坐地鐵上班,在北京住,從不堵車","headUrl":"https://www.zhuifengren.cn/img/xwe.jpg"} } }
3.8 依據文檔ID刪除文檔
import cn.zhuifengren.myelasticsearch.pojo.User; import cn.zhuifengren.myelasticsearch.utils.JsonUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.IndexQuery; import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder; import org.springframework.data.elasticsearch.core.query.UpdateQuery; import org.springframework.data.elasticsearch.core.query.UpdateResponse; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; @SpringBootTest(classes = MyelasticsearchApplication.class) public class ElasticsearchTest { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @Test public void delete() { String result = elasticsearchRestTemplate.delete("2", User.class); System.out.println(JsonUtils.objectToJson(result)); // 結果:"2" } }
3.9 分頁檢索
import cn.zhuifengren.myelasticsearch.pojo.User; import cn.zhuifengren.myelasticsearch.utils.JsonUtils; import org.elasticsearch.index.query.QueryBuilders; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.*; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; @SpringBootTest(classes = MyelasticsearchApplication.class) public class ElasticsearchTest { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @Test public void searchForPage() { Pageable pageable = PageRequest.of(0,10); // page 從第 0 頁開始 Query query = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchQuery("desc", "一名小學生")) .withQuery(QueryBuilders.termQuery("age", 10)) .withPageable(pageable) .build(); SearchHits<User> result = elasticsearchRestTemplate.search(query, User.class); System.out.println(JsonUtils.objectToJson(result)); } }
結果Json:
{ "totalHits": 1, "totalHitsRelation": "EQUAL_TO", "maxScore": 1, "scrollId": null, "searchHits": [ { "index": "index_user", "id": "3", "score": 1, "sortValues": [], "content": { "userId": "3", "name": "迪士尼在逃仙柔", "loginName": "dsnzxr", "age": 10, "birthday": 1308672000000, "desc": "我是一名五年級的小學生,每天專車接專車送,中午在學校入伙,食堂菜可好了,上學期期末考試我拿了三好學生獎", "headUrl": "https://www.zhuifengren.cn/img/dsnzxr.jpg" }, "highlightFields": {}, "innerHits": {}, "nestedMetaData": null, "routing": null, "explanation": null, "matchedQueries": [] } ], "aggregations": null, "empty": false }
3.10 高亮的實現
import cn.zhuifengren.myelasticsearch.pojo.User; import cn.zhuifengren.myelasticsearch.utils.JsonUtils; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.*; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; @SpringBootTest(classes = MyelasticsearchApplication.class) public class ElasticsearchTest { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @Test public void highlight() { Pageable pageable = PageRequest.of(0,10); // page 從第 0 頁開始 HighlightBuilder.Field highlightField = new HighlightBuilder.Field("desc") .preTags("<span>") .postTags("</span>"); Query query = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchQuery("desc", "一名小學生")) .withHighlightFields(highlightField) .withPageable(pageable) .build(); SearchHits<User> result = elasticsearchRestTemplate.search(query, User.class); System.out.println(JsonUtils.objectToJson(result)); } }
結果Json:
{ "totalHits": 3, "totalHitsRelation": "EQUAL_TO", "maxScore": 3.0418546, "scrollId": null, "searchHits": [ { "index": "index_user", "id": "3", "score": 3.0418546, "sortValues": [], "content": { "userId": "3", "name": "迪士尼在逃仙柔", "loginName": "dsnzxr", "age": 10, "birthday": 1308672000000, "desc": "我是一名五年級的小學生,每天專車接專車送,中午在學校入伙,食堂菜可好了,上學期期末考試我拿了三好學生獎", "headUrl": "https://www.zhuifengren.cn/img/dsnzxr.jpg" }, "highlightFields": { "desc": [ "我是<span>一名</span>五年級的<span>小學生</span>,每天專車接專車送,中午在學校入伙,食堂菜可好了,上學期期末考試我拿了三好<span>學生</span>獎" ] }, "innerHits": {}, "nestedMetaData": null, "routing": null, "explanation": null, "matchedQueries": [] }, { "index": "index_user", "id": "1", "score": 0.5957724, "sortValues": [], "content": { "userId": "1", "name": "僵屍獵手", "loginName": "jsls", "age": 25, "birthday": 636220800000, "desc": "我是一名房產經紀人,現在轉行了,目前是一名運輸工人", "headUrl": "https://www.zhuifengren.cn/img/jsls.jpg" }, "highlightFields": { "desc": [ "我是<span>一名</span>房產經紀人,現在轉行了,目前是<span>一名</span>運輸工人" ] }, "innerHits": {}, "nestedMetaData": null, "routing": null, "explanation": null, "matchedQueries": [] }, { "index": "index_user", "id": "2", "score": 0.46563908, "sortValues": [], "content": { "userId": "2", "name": "夏維爾", "loginName": "xwe", "age": 28, "birthday": 707760000000, "desc": "我是一名高級開發經理,每天坐地鐵上班,在北京住,從不堵車", "headUrl": "https://www.zhuifengren.cn/img/xwe.jpg" }, "highlightFields": { "desc": [ "我是<span>一名</span>高級開發經理,每天坐地鐵上班,在北京住,從不堵車" ] }, "innerHits": {}, "nestedMetaData": null, "routing": null, "explanation": null, "matchedQueries": [] } ], "aggregations": null, "empty": false }
3.11 自定義排序的實現
import cn.zhuifengren.myelasticsearch.pojo.User; import cn.zhuifengren.myelasticsearch.utils.JsonUtils; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortBuilder; import org.elasticsearch.search.sort.SortOrder; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.core.document.Document; import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates; import org.springframework.data.elasticsearch.core.query.*; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.Map; @SpringBootTest(classes = MyelasticsearchApplication.class) public class ElasticsearchTest { @Autowired private ElasticsearchRestTemplate elasticsearchRestTemplate; @Test public void sort() { Pageable pageable = PageRequest.of(0,10); // page 從第 0 頁開始 HighlightBuilder.Field highlightField = new HighlightBuilder.Field("desc") .preTags("<span>") .postTags("</span>"); SortBuilder<FieldSortBuilder> sortBuilder = new FieldSortBuilder("age").order(SortOrder.DESC); Query query = new NativeSearchQueryBuilder() .withQuery(QueryBuilders.matchQuery("desc", "一名小學生")) .withHighlightFields(highlightField) .withSort(sortBuilder) // 排序可加多個 .withPageable(pageable) .build(); SearchHits<User> result = elasticsearchRestTemplate.search(query, User.class); System.out.println(JsonUtils.objectToJson(result)); } }
4. 綜述
今天聊了一下 Elasticsearch7.14.1(ES 7.14.1)與 springboot2.5.4 的整合,希望可以對大家的工作有所幫助。
歡迎幫忙點贊、評論、轉發、加關注 :)
關注追風人聊Java,每天更新Java干貨。