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:9200elasticsearch: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 {@Autowiredprivate ElasticsearchRestTemplate elasticsearchRestTemplate;@Testpublic 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 {@Autowiredprivate ElasticsearchRestTemplate elasticsearchRestTemplate;@Testpublic 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 {@Autowiredprivate ElasticsearchRestTemplate elasticsearchRestTemplate;@Testpublic 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 {@Autowiredprivate ElasticsearchRestTemplate elasticsearchRestTemplate;@Testpublic 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 {@Autowiredprivate ElasticsearchRestTemplate elasticsearchRestTemplate;@Testpublic 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 {@Autowiredprivate ElasticsearchRestTemplate elasticsearchRestTemplate;@Testpublic 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 {@Autowiredprivate ElasticsearchRestTemplate elasticsearchRestTemplate;@Testpublic 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干货。