赞
踩
引入es的依赖库
<!-- elasticsearch-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
创建一个接口来从es里面查询数据 service文件中
/**
* 从 ES 查询
*
* @param postQueryRequest
* @return
*/
Page<Post> searchFromEs(PostQueryRequest postQueryRequest);
// postQueryRequest是封装的请求参数类,主要需要传入的字段就是searchText(搜索关键词)
实现该接口 (ES负责的是静态查询,将查询结果对应的文档id找到之后再返回到mysql里面查询更加完整的数据)
public Page<Post> searchFromEs(PostQueryRequest postQueryRequest) { // 01. 将所有的参数给单独提取出来 String searchText = postQueryRequest.getSearchText(); // 02. 指定查询的数据的页数 es 起始页为 0 long current = postQueryRequest.getCurrent() - 1; long pageSize = postQueryRequest.getPageSize(); // 03. 创建一个查询对象 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); // 04. 查询条件过滤 boolQueryBuilder.filter(QueryBuilders.termQuery("isDelete", 0)); // 05. 判断是否有传入以下的查询条件,如果有的话就加入到查询条件中 // 按关键词检索 if (StringUtils.isNotBlank(searchText)) { boolQueryBuilder.should(QueryBuilders.matchQuery("title", searchText)); boolQueryBuilder.should(QueryBuilders.matchQuery("description", searchText)); boolQueryBuilder.should(QueryBuilders.matchQuery("content", searchText)); boolQueryBuilder.minimumShouldMatch(1); // 至少匹配一个 } // 分页 PageRequest pageRequest = PageRequest.of((int) current, (int) pageSize); // 构造排序的查询 // NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder) // .withPageable(pageRequest).withSorts(sortBuilder).build(); // 构造没有排序的查询 NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(boolQueryBuilder) .withPageable(pageRequest).build(); // 将所有的查询结果都取出来 SearchHits<PostEsDTO> searchHits = elasticsearchRestTemplate.search(searchQuery, PostEsDTO.class); Page<Post> page = new Page<>(); // 将查询的结果添加到page对象里面 page.setTotal(searchHits.getTotalHits()); List<Post> resourceList = new ArrayList<>(); // 查出结果后,从 db 获取最新动态数据(比如点赞数) es负责进行静态数据的筛选,然后在回表到mysql里面将所有的数据信息查出来 if (searchHits.hasSearchHits()) { List<SearchHit<PostEsDTO>> searchHitList = searchHits.getSearchHits(); // 将查询到的文档id使用列表进行存储 List<Long> postIdList = searchHitList.stream().map(searchHit -> searchHit.getContent().getId()) .collect(Collectors.toList()); // 根据这个文档id查询 mysql里面的数据 使用列表存储 List<Post> postList = baseMapper.selectBatchIds(postIdList); if (postList != null) { // 根据文章id进行分组 Map<Long, List<Post>> idPostMap = postList.stream().collect(Collectors.groupingBy(Post::getId)); // 如果查询到的mysql的集合里面包含上面es中查询到的文档id就取出来放到resource列表中存储 postIdList.forEach(postId -> { if (idPostMap.containsKey(postId)) { resourceList.add(idPostMap.get(postId).get(0)); } else { // 从 es 清空 db 已物理删除的数据 不包含的话就直接删除 String delete = elasticsearchRestTemplate.delete(String.valueOf(postId), PostEsDTO.class); log.info("delete post {}", delete); } }); } } // 设置到records中支持分页存储 page.setRecords(resourceList); return page; }
然后在需要查询es里面数据的地方调用该接口就行
@Override
public Page<PostVO> doSearch(String searchText, int pageSize, int pageNum) {
PostQueryRequest postQueryRequest = new PostQueryRequest();
postQueryRequest.setSearchText(searchText);
postQueryRequest.setPageSize(pageSize);
postQueryRequest.setCurrent(pageNum);
// 这里因为不能在传入request参数 所以就将request参数从requestHolder里面获取
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
// 将查询帖子的接口转到es里面去进行查询 调用es查询的接口
Page<Post> postPage = postService.searchFromEs(postQueryRequest);
Page<PostVO> postVOPage = postService.getPostVOPage(postPage, request);
// Page<PostVO> postResult = postService.listPostVOByPage(postQueryRequest, request);
return postVOPage;
}
上面的接口实现出现了一个问题,就是我们的es里面根本就没有任何的数据,所以也就无从查起了
创建一个es表的实体类
package com.yupi.springbootinit.model.dto.post; import cn.hutool.core.collection.CollUtil; import cn.hutool.json.JSONUtil; import com.yupi.springbootinit.model.entity.Post; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; 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 java.io.Serializable; import java.util.Date; import java.util.List; /** * 帖子 ES 包装类 * **/ // todo 取消注释开启 ES(须先配置 ES) @Document(indexName = "post") // es的文档索引(表名) @Data public class PostEsDTO implements Serializable { private static final String DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; /** * id 必须打上id注解 */ @Id private Long id; /** * 标题 */ private String title; /** * 内容 */ private String content; /** * 标签列表 */ private List<String> tags; /** * 创建用户 id */ private Long userId; /** * 创建时间 解析时间使得java时间和es时间符合 */ @Field(index = false, store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN) private Date createTime; /** * 更新时间 */ @Field(index = false, store = true, type = FieldType.Date, format = {}, pattern = DATE_TIME_PATTERN) private Date updateTime; /** * 是否删除 */ private Integer isDelete; private static final long serialVersionUID = 1L; /** * 对象转包装类 * * @param post * @return */ public static PostEsDTO objToDto(Post post) { if (post == null) { return null; } PostEsDTO postEsDTO = new PostEsDTO(); BeanUtils.copyProperties(post, postEsDTO); String tagsStr = post.getTags(); if (StringUtils.isNotBlank(tagsStr)) { postEsDTO.setTags(JSONUtil.toList(tagsStr, String.class)); } return postEsDTO; } /** * 包装类转对象 * * @param postEsDTO * @return */ public static Post dtoToObj(PostEsDTO postEsDTO) { if (postEsDTO == null) { return null; } Post post = new Post(); BeanUtils.copyProperties(postEsDTO, post); List<String> tagList = postEsDTO.getTags(); if (CollUtil.isNotEmpty(tagList)) { post.setTags(JSONUtil.toJsonStr(tagList)); } return post; } }
创建一个全量的同步任务(只在项目启动的时候进行一次同步,用完之后记得将注解注释掉,这样下次就不会再执行)
// todo 取消注释开启任务 //@Component @Slf4j // CommandLineRunner是一个接口用于在程序启动之后进行一些初始化方法执行,可以重写里面的run方法即可 public class FullSyncPostToEs implements CommandLineRunner { @Resource private PostService postService; @Resource private PostEsDao postEsDao; @Override public void run(String... args) { // 01. 查询里面所有的数据 List<Post> postList = postService.list(); if (CollUtil.isEmpty(postList)) { return; } // 02. 将post查询的所有数据的tags取出来 转换为一个新的对象PostEsDTO 同时将里面的tags由json格式转为string格式 List<PostEsDTO> postEsDTOList = postList.stream().map(post -> PostEsDTO.objToDto(post)).collect(Collectors.toList()); // List<PostEsDTO> postEsDTOList = postList.stream().map(PostEsDTO::objToDto).collect(Collectors.toList()); // 03. 一次最多同步500条数据到es里面 final int pageSize = 500; int total = postEsDTOList.size(); log.info("FullSyncPostToEs start, total {}", total); for (int i = 0; i < total; i += pageSize) { int end = Math.min(i + pageSize, total); log.info("sync from {} to {}", i, end); postEsDao.saveAll(postEsDTOList.subList(i, end)); } log.info("FullSyncPostToEs end, total {}", total); } }
增量同步(通过定时任务的方式来判断数据更新的时间从而进行数据的同步)
@Component @Slf4j public class IncSyncPostToEs { @Resource private PostMapper postMapper; @Resource private PostEsDao postEsDao; /** * 每分钟执行一次 */ @Scheduled(fixedRate = 60 * 1000) public void run() { // 查询5分钟内的数据 Date fiveMinutesAgoDate = new Date(new Date().getTime() - 5 * 60 * 1000L); // sql查询 写在mapper里面的 List<Post> postList = postMapper.listPostWithDelete(fiveMinutesAgoDate); if (CollUtil.isEmpty(postList)) { log.info("no inc post"); return; } // 将数据进行转换为新的对象格式 PostEsDTO类型 List<PostEsDTO> postEsDTOList = postList.stream() .map(PostEsDTO::objToDto) .collect(Collectors.toList()); // 最多只能同步500条数据 final int pageSize = 500; int total = postEsDTOList.size(); log.info("IncSyncPostToEs start, total {}", total); for (int i = 0; i < total; i += pageSize) { int end = Math.min(i + pageSize, total); log.info("sync from {} to {}", i, end); postEsDao.saveAll(postEsDTOList.subList(i, end)); } log.info("IncSyncPostToEs end, total {}", total); } }
- 首先引入对应的ES依赖库
- 然后进行ES的数据同步(创建ES实体封装类,编写定时任务)
- 数据同步成功之后编写对应的查询接口以及接口实现类,实现类里面具体执行对es数据库的查询【注意这里返回的不是es里面的数据,而是返回的关联的文档ID,将该ID取出来之后去mysql数据库里面查询和ID相等的完整的动态数据才是我们需要的结果】
- 调用接口获取数据,响应给前端即可
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。