Springboot整合ElasticSearch实现搜索功能

embedded/2024/12/21 16:09:52/

一、导入依赖

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId><version>7.12.1</version>
</dependency>
<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.12.1</version>
</dependency>

二、编写实体类

在实现方法前,我们需要有一个和存入ES索引库里数据字段对应的实体类,方便从数据库里查询的对应字段的数据,并存入到ES中。

@Data
public class EsItemInfoDoc implements Serializable {private String itemId;private String title;private String cover;private String userId;private String nickname;private String avatar;private String categoryId;private String boardName;private Integer viewCount;private Integer goodCount;private Integer commentCount;private Integer collectCount;private String createTime;
}

三、编写ES工具

在component文件夹下,创建一个ElasticSearchComponent.java 组件类,提供了索引创建、文档保存、更新、删除以及搜索等功能,具体如下:

1.索引相关操作

  • isExistsIndex 方法用于检查指定名称(通过 AppConfig 获取)的索引是否存在。
  • createIndex 方法首先调用 isExistsIndex 判断索引是否已存在,若不存在则创建索引,创建时使用了硬编码的 MAPPING_TEMPLATE 字符串来定义索引的映射结构,若创建成功或索引已存在会记录相应日志,创建失败则抛出异常。

2.文档操作相关 

  • saveEsItemInfoDoc 方法根据传入的 EsItemInfoDoc 对象的 itemId 判断文档是否存在,若存在则调用 updateEsItemInfoDoc 方法更新文档,不存在则将其保存到 Elasticsearch 中。
  • docExist 方法通过 Elasticsearch 的 GetRequest 和 GetResponse 判断指定 docId 的文档是否存在。
  • updateEsItemInfoDoc 方法用于更新已存在的文档,先将文档的 createTime 字段设为 null,然后通过反射获取文档对象的非空字段及对应值,构建 Map 后使用 UpdateRequest 更新文档,若要更新的数据为空则直接返回。
  • updateEsDocFieldCount 方法用于更新指定文档(通过 docId 指定)中某个字段(通过 fieldName 指定)的计数值(通过 count 指定),通过编写 Script 脚本实现字段值的增量更新操作。
  • deleteEsItemInfoDoc 方法用于删除指定 itemId 的文档,通过 DeleteRequest 向 Elasticsearch 发起删除请求。

3.搜索功能 

search 方法实现了根据关键词、排序类型、页码、每页大小等条件进行搜索的功能。它先构建 SearchSourceBuilder,设置查询条件(使用 multiMatchQuery 对多个字段进行关键词匹配查询),根据传入参数决定是否添加高亮显示、设置排序规则以及分页信息,然后执行搜索请求并解析返回结果,将搜索到的文档数据转换为 EsItemInfoDoc 列表,最后封装成 PageResult 对象返回。 

完整代码

@Component("elasticSearchComponent")
@Slf4j
public class ElasticSearchComponent {@Resourceprivate AppConfig appConfig;@Resourceprivate RestHighLevelClient restHighLevelClient;private Boolean isExistsIndex() throws IOException {GetIndexRequest request = new GetIndexRequest(appConfig.getEsIndexName());return restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);}public void createIndex() throws Exception {try {if (isExistsIndex()) {return;}CreateIndexRequest request = new CreateIndexRequest(appConfig.getEsIndexName());//request.settings(MAPPING_TEMPLATE, XContentType.JSON);request.source(MAPPING_TEMPLATE, XContentType.JSON);CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);boolean acknowledged = response.isAcknowledged();if (!acknowledged) {throw new BaseException("创建索引失败");} else {log.info("创建索引成功");}} catch (Exception e) {log.error("创建索引失败", e);throw new BaseException("创建索引失败");}}private static final String MAPPING_TEMPLATE = """{"mappings": {"properties": {"itemId": {"type": "keyword","index": false},"title":{"type": "text","analyzer": "ik_max_word"},"cover": {"type": "text","index": false},"userId":{"type": "keyword","index": false},"nickname":{"type": "text","analyzer": "ik_max_word"},"avatar": {"type": "text","index": false},"categoryId":{"type": "keyword","index": false},"boardName":{"type": "text","analyzer": "ik_max_word"},"viewCount":{"type": "integer","index": false},"commentCount":{"type": "integer","index": false},"goodCount":{"type": "integer","index": false},"collectCount":{"type": "integer","index": false},"createTime":{"type": "date","format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis","index": false},"database":{"type": "keyword","index": false}}}}""";public void saveEsItemInfoDoc(EsItemInfoDoc esItemInfoDoc) {try {if (docExist(esItemInfoDoc.getItemId())) {log.info("esItemInfoDoc已存在,更新esItemInfoDoc");updateEsItemInfoDoc(esItemInfoDoc);} else {log.info("esItemInfoDoc不存在,保存esItemInfoDoc");IndexRequest request = new IndexRequest(appConfig.getEsIndexName()).id(esItemInfoDoc.getItemId()).source(JSONUtil.toJsonStr(esItemInfoDoc), XContentType.JSON);restHighLevelClient.index(request, RequestOptions.DEFAULT);}} catch (Exception e) {log.error("保存esItemInfoDoc失败", e);throw new RuntimeException("保存esItemInfoDoc失败", e);}}private Boolean docExist(String docId) throws IOException {GetRequest request = new GetRequest(appConfig.getEsIndexName(), docId);GetResponse response = restHighLevelClient.get(request, RequestOptions.DEFAULT);return response.isExists();}private void updateEsItemInfoDoc(EsItemInfoDoc esItemInfoDoc) {try {esItemInfoDoc.setCreateTime(null);Map<String, Object> dataMap = new HashMap<>();Field[] fields = esItemInfoDoc.getClass().getDeclaredFields();for (Field field : fields) {String methodName = "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);Method method = esItemInfoDoc.getClass().getMethod(methodName);Object object = method.invoke(esItemInfoDoc);if (object != null && object instanceof String && !object.toString().isEmpty()|| object != null && !(object instanceof String)) {dataMap.put(field.getName(), object);}}if (dataMap.isEmpty()) {return;}UpdateRequest updateRequest = new UpdateRequest(appConfig.getEsIndexName(), esItemInfoDoc.getItemId());updateRequest.doc(dataMap);restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);} catch (Exception e) {log.error("更新esItemInfoDoc失败", e);throw new RuntimeException("更新esItemInfoDoc失败", e);}}public void updateEsDocFieldCount(String docId, String fieldName, Integer count) {try {UpdateRequest updateRequest = new UpdateRequest(appConfig.getEsIndexName(), docId);// 创建Script对象,用于定义更新文档字段的脚本逻辑Script script = new Script(ScriptType.INLINE, "painless", "ctx._source."+ fieldName + " += params.count", Collections.singletonMap("count", count));// 将脚本设置到更新请求中updateRequest.script(script);restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);} catch (Exception e) {log.error("更新数量到esDocFieldCount失败", e);throw new RuntimeException("更新esDocFieldCount失败", e);}}public void deleteEsItemInfoDoc(String itemId) {try {DeleteRequest request = new DeleteRequest(appConfig.getEsIndexName(), itemId);restHighLevelClient.delete(request, RequestOptions.DEFAULT);} catch (Exception e) {log.error("删除esItemInfoDoc失败", e);throw new RuntimeException("删除esItemInfoDoc失败", e);}}public PageResult<EsItemInfoDoc> search(Boolean highLight, String keyword, Integer orderType, Integer pageNo, Integer pageSize) {try {//创建搜索请求SearchOrderTypeEnum searchOrderTypeEnum = SearchOrderTypeEnum.getOrderType(orderType);SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();searchSourceBuilder.query(QueryBuilders.multiMatchQuery(keyword, "title", "boardName", "nickname"));//高亮if (highLight) {HighlightBuilder highlightBuilder = new HighlightBuilder();highlightBuilder.field("title");highlightBuilder.field("boardName");highlightBuilder.field("nickname");highlightBuilder.preTags("<span style='color:red'>");highlightBuilder.postTags("</span>");searchSourceBuilder.highlighter(highlightBuilder);}//排序searchSourceBuilder.sort("_score", SortOrder.ASC);if (orderType != null) {searchSourceBuilder.sort(searchOrderTypeEnum.getField(), SortOrder.DESC);}//分页pageNo = pageNo == null ? 1 : pageNo;pageSize = pageSize == null ? 24 : pageSize;searchSourceBuilder.size(pageSize);searchSourceBuilder.from((pageNo - 1) * pageSize);//执行搜索SearchRequest searchRequest = new SearchRequest(appConfig.getEsIndexName());searchRequest.source(searchSourceBuilder);SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);//解析结果SearchHits searchHits = searchResponse.getHits();Integer totalCount = (int) searchHits.getTotalHits().value;List<EsItemInfoDoc> esItemInfoDocList = new ArrayList<>();for (SearchHit hit : searchHits.getHits()) {EsItemInfoDoc esItemInfoDoc = JSONUtil.toBean(hit.getSourceAsString(), EsItemInfoDoc.class);if (highLight) {Map<String, HighlightField> highlightFields = hit.getHighlightFields();HighlightField title = highlightFields.get("title");HighlightField boardName = highlightFields.get("boardName");HighlightField nickname = highlightFields.get("nickname");if (title != null) {esItemInfoDoc.setTitle(title.getFragments()[0].toString());}if (boardName != null) {esItemInfoDoc.setBoardName(boardName.getFragments()[0].toString());}if (nickname != null) {esItemInfoDoc.setNickname(nickname.getFragments()[0].toString());}}esItemInfoDocList.add(esItemInfoDoc);}SimplePage page = new SimplePage(pageNo, pageSize, totalCount);PageResult<EsItemInfoDoc> pageResult = new PageResult<>(totalCount,page.getPageSize(),page.getPageNo(),page.getPageTotal(),esItemInfoDocList);return pageResult;} catch (Exception e) {log.error("搜索失败", e);throw new RuntimeException("搜索失败", e);}}}

四、创建初始化工具类

由于考虑到迁移性,我们可以定义一个初始化的工具类,让程序每次运行的时候调用elasticSearchComponent中创建索引的方法进行索引的创建,避免程序在一个新的环境运行时,ES中没有对应的索引库。

@Component
public class InitRun implements ApplicationRunner {@Resourceprivate  ElasticSearchComponent elasticSearchComponent;@Overridepublic void run(ApplicationArguments args) throws Exception {elasticSearchComponent.createIndex();}
}

五、使用

如果,我们需要使用elasticSearchComponent里面的功能。我们只需要先注入elasticSearchComponent ,然后直接调用里面的方法就行了。

比如说,搜索功能:

@Slf4j
@RestController
@RequestMapping("/search")
public class SearchController {@Autowiredprivate ElasticSearchComponent elasticSearchComponent;@GetMappingpublic Result search(@RequestParam String keyword,@RequestParam Integer orderType,   @RequestParam Integer pageNo,@RequestParam Integer pageSize) {log.info("搜索词:{}, 排序方式:{}, 页码:{}, 页大小:{}", keyword, orderType, pageNo, pageSize);//从es中搜索PageResult<EsItemInfoDoc> pageResult = elasticSearchComponent.search(true, keyword, orderType, pageNo, pageSize);return Result.success(pageResult);}

为了大家可以使用,案例中涉及到的其他代码,如下:


分页结果代码

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PageResult<T> implements Serializable {private static final long serialVersionUID = 1L;private Integer totalCount;private Integer pageSize;private Integer pageNo;private Integer pageTotal;private List<T> list = new ArrayList<T>();}

ES搜索中分页数据填充代码

@Data
public class SimplePage {private int pageNo;private int pageSize;private int countTotal;private int pageTotal;private int start;private int end;public SimplePage() {}public SimplePage(Integer pageNo, int pageSize, int countTotal) {if (pageNo == null) {pageNo = 0;}this.pageNo = pageNo;this.pageSize = pageSize;this.countTotal = countTotal;action();}public SimplePage(int start, int end) {this.start = start;this.end = end;}public void action() {if (this.pageNo <= 0) {this.pageNo = 12;}if (this.countTotal >= 0) {this.pageTotal = this.countTotal % this.pageSize == 0 ? this.countTotal / this.pageSize: this.countTotal / this.pageSize + 1;} else {pageTotal = 1;}if (pageNo <= 1) {pageNo = 1;}if (pageNo > pageTotal) {pageNo = pageTotal;}this.start = (pageNo - 1) * pageSize;this.end = this.pageSize;}
}

以上功能,我的项目中是可以用的。如果有什么地方不对的地方,谢谢大家的指教。


http://www.ppmy.cn/embedded/147565.html

相关文章

[前端]mac安装nvm(node.js)多版本管理

一、下载nvm https://github.com/nvm-sh/nvm/releases/tag/v0.40.1 二、安装nvm 解压后 ./install.sh nvm -v 0.40.1安装后&#xff0c;验证一下版本&#xff0c;搞定&#xff01; 接下来开始安装node.js 三、安装node.js 1、查看一下有哪些版本可以安装&#…

VSCode编辑+GCC for ARM交叉编译工具链+CMake构建+OpenOCD调试(基于STM32的标准库/HAL库)

一、CMake安装 进入CMake官网的下载地址Get the Software&#xff0c;根据系统安装对应的Binary distributions。 或者在CMake——国内镜像获取二进制镜像安装包。 或者访问GitHub的xPack项目xPack CMake v3.28.6-1&#xff0c;下载即可。 记得添加用户/系统的环境变量&#…

MySQL系列之数据类型(Numeric)

导览 前言一、数值类型综述二、数值类型详解1. NUMERIC1.1 UNSIGNED或SIGNED1.2 数据类型划分 2. Integer类型取值和存储要求3. Fixed-Point类型取值和存储要求4. Floating-Point类型取值和存储要求 结语精彩回放 前言 MySQL系列最近三篇均关注了和我们日常工作或学习密切相关…

存储过程(详细-附样例)

所有样例都是在mysql5.7版本下执行的&#xff0c;如果用其他数据库&#xff0c;请根据对应的数据库存储过程定义更改语句。 主体目录 一、存储过程相关命令 二、存储过程 2、参数 3、变量 4、if 流程控制 5、case 条件控制 6、while 循环语句 7、repeat 循环语句 8、…

云计算HCIP-OpenStack03

书接上回&#xff1a; 云计算HCIP-OpenStack02-CSDN博客 10.KeyStone keystone-Openstack&#xff0c;IAM服务&#xff08;统一身份认证&#xff09;-云服务 建议先去了解Hadoop&#xff08;大数据生态系统&#xff09;中的kerberos&#xff08;LDAPkerberos的鉴权机制&#xf…

vue-element-admin npm install 安装失败,tui-editor更名导致

导语&#xff1a; 本失败原因是由于tui-editor&#xff08;富文本编辑器插件&#xff09;更名造成的&#xff0c;现在已经更名为toast-ui/editor&#xff1b; 在一个是一直以为是我的git问题 报错代码&#xff1a;code 128 ..........&#xff0c;困扰了我好长时间&#xff…

分布式链路追踪简介-01-dapper 论文思想介绍

开源项目 auto-log 自动日志输出 分布式链路追踪简介 随着业务系统的不断发展、微服务架构的演进&#xff0c;从原来的单体应用架构、垂直应用架构、分布式 SOA 架构到现在的微服务架构&#xff0c;系统逐步走向微服务化以适应用户高并发请求等需求。 在微服务架构中&#…

【C#】方法参数的修饰符ref 与 out

在 C# 中&#xff0c;ref 和 out 是方法参数的修饰符&#xff0c;用于将参数 按引用传递&#xff0c;而不是按值传递。这允许方法修改调用者传递的变量的值。尽管它们的行为类似&#xff0c;但有重要的区别和适用场景。 1. ref 的含义与使用 含义 引用传递&#xff1a; 参数通…