当前位置:   article > 正文

开发搜索功能(ES支持)

开发搜索功能(ES支持)

Elasticsearch加载数据

我们要想完成高效的搜索任务,需要ES的支持

因为数据库的模糊查询效率太低

我们在前端页面中完成的搜索是从ES中搜索数据

这样就要求我们在查询之前,需要先将商品信息 (SPU) 保存到ES中

一开始我们采用最原始的方法: 从数据库查询出数据之后新增到ES中

确认实体类

搜索功能编写在mall-search模块中

它使用的实体类在cn.tedu.mall.pojo.search.eneity包下SpuForElastic

这个类有4个字段是具备分词功能的

所以支持我们使用这4个字段进行查询

  1. /**
  2. * SPU名称
  3. */
  4. @Field(name = "name",type = FieldType.Text,
  5. analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
  6. @ApiModelProperty(value="SPU名称")
  7. private String name;
  8. //.....
  9. /**
  10. * 标题
  11. */
  12. @Field(name="title",type = FieldType.Text,
  13. analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
  14. @ApiModelProperty(value="标题")
  15. private String title;
  16. /**
  17. * 简介
  18. */
  19. @Field(name="description",type = FieldType.Text,
  20. analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
  21. @ApiModelProperty(value="简介")
  22. private String description;
  23. //.....
  24. /**
  25. * 类别名称(冗余)
  26. */
  27. @Field(name="category_name",type = FieldType.Text,
  28. analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
  29. @ApiModelProperty(value="类别名称(冗余)")
  30. private String categoryName;
  31. //.....

开发ES的持久层

仍然使用SpringDataElasticsearch框架来操作ES

按照SpringData的规范,我们创建包repository

在这个包中创建SpuForElasticRepository接口,代码如下

  1. // SpuForElastic实体类操作ES的持久层接口
  2. // 需要继承SpringData给定的父接口,继承之后可以直接使用提供的基本增删改查方法
  3. @Repository
  4. public interface SpuForElasticRepository extends
  5.                             ElasticsearchRepository<SpuForElastic,Long>{
  6. }

这个接口提供了批量新增数据到ES的方法

但是要想获得数据库中所有pms_spu表的数据,必须连接数据库查询这些数据

但是search模块是负责管理ES的,所以需要Dubbo调用product模块获取这些数据

product模块提供的查询功能

经过观察发现业务逻辑逻辑层调用ForFrontSpuServiceImpl类中

具有一个getSpuByPage的方法

他分页查询所有spu信息

  1. @Override
  2. public JsonPage<Spu> getSpuByPage(Integer pageNum, Integer pageSize) {
  3. PageHelper.startPage(pageNum,pageSize);
  4. List<Spu> list=spuMapper.findAllList();
  5. return JsonPage.restPage(new PageInfo<>(list));
  6. }

分页的原因是一般加载到ES中的数据量非常大(几十万上百万条),我们不可能一次性将所有数据查询出来,增到ES中,必须分批分次

分页查询就是典型的分批查询,每次查询一部分数据,通过循环遍历,将每页数据都增到ES中

Search模块执行加载

mall-search-webapi模块创建service.impl包

包中创建SearchServiceImpl类,用于将数据库中的数据加载到ES中

代码如下

  1. @Service
  2. @Slf4j
  3. public class SearchServiceImpl implements ISearchService{
  4.    
  5.     // dubbo调用Product模块分页查询所有spu
  6.     @DubboReference
  7. private IForFrontSpuService dubboSpuService;
  8. @Autowired
  9. private SpuForElasticRepository spuRepository;
  10.     @Override
  11.     public void loadSpuByPage(){
  12.         // 循环完成分页查询所有数据,
  13. // 每循环一次,将查询到的当页数据新增到ES,直到最后一页
  14. // 因为是需要运行一次之后,才知道总页数,所以这里采用do-while循环结构
  15.         int i = 1;    // 循环变量i,从1开始,因为可以同时用作页码值
  16.         int pages;    // 总页数,在循环进行一次之后,才能被赋值,这里可以只声明或赋默认值
  17.        
  18.         do{
  19.             // dubbo调用查询当前页的spu数据
  20.             JsonPage<Spu> spus=dubboSpuService.getSpuByPage(i,2);
  21.             // 我们从数据查询出来的类型Spu不能直接向ES中执行新增
  22. // 需要转换为SpuForElastic类型,所以我们先声明这样类型的集合
  23.             List<SpuForElastic> esSpus=new ArrayList<>();
  24.             // 遍历数据库中查询出的当页数据
  25.             for(Spu spu : spus.getList()){
  26. // 下面开始转换,实例化新实体类,并将同名属性赋值给它
  27. SpuForElastic esSpu=new SpuForElastic();
  28. BeanUtils.copyProperties(spu,esSpu);
  29. // 赋值完成后,添加到上面的集合中!
  30. esSpus.add(esSpu);
  31. }
  32. // esSpus集合中已经包含了本次查询的所有数据,下面执行批量新增到ES的操作
  33. spuRepository.saveAll(esSpus);
  34. log.info("成功加载了第{}页数据",i);
  35. // 下次循环i值自增
  36. i++;
  37. // 给pages赋值总页数
  38. pages=spus.getTotalPage();
  39.         }while(i<=pages);
  40.     }
  41.     @Override
  42. public JsonPage<SpuForElastic> search(String keyword, Integer page, Integer pageSize) {
  43. return null;
  44. }
  45. }

创建测试类运行即可

  1. // 下面注解必须加!!!!!!
  2. @SpringBootTest
  3. public class SpuElasticTest {
  4. @Autowired
  5. private ISearchService searchService;
  6. @Test
  7. void loadData(){
  8. searchService.loadSpuByPage();
  9. System.out.println("ok");
  10. }
  11. }

运行测试前保证

Nacos\Seata\\**ES**启动

启动product模块

运行测试,没有报错即可

验证ES中的数据

我们再通过连接ES来进行全查

检验上面执行的加载工作是否达到效果

仍然在测试类中,再编写一个方法,使用SpringData提供的全查方法查询后遍历输出

检查输出内容,代码如下

  1. @Autowired
  2. private SpuForElasticRepository spuRepository;
  3. @Test
  4. void showData(){
  5. Iterable<SpuForElastic> spus=spuRepository.findAll();
  6. spus.forEach(spu -> System.out.println(spu));
  7. }

搜索功能的实现

电商网站一定会有按用户输入的关键字进行搜索的功能

这样的搜索都是搜索ES查询到的结果

上面我们已经将所有spu信息保存到了ES中

下面通过查询逻辑将搜索结果显示出来

编写SpringData自定义查询

如果我们按照关键字"手机"进行搜索

可以在Repository接口中编写自定义方法

  1. @Repository
  2. public interface SpuForElasticRepository extends
  3. ElasticsearchRepository<SpuForElastic,Long> {
  4. // 查询title字段包含指定关键字(分词)的spu数据
  5. Iterable<SpuForElastic> querySpuForElasticsByTitleMatches(String title);
  6. }

上面的查询可以通过测试类测试

  1. @Test
  2. void getSpuByTitle(){
  3. // 根据title指定的分词查询数据
  4. Iterable<SpuForElastic> spus=
  5. spuRepository.querySpuForElasticsByTitleMatches("手机");
  6. spus.forEach(spu -> System.out.println(spu));
  7. }

尤其需要关注ES是否已经启动

不需要其它项目的支持,直接运行测试即可

我们业务中需要4个字段的条件查询,是可以通过方法名称的编写实现的

SpringData也支持我们在代码中编写查询语句,以避免过长的方法名

  1. @Query("{\n" +
  2. " \"bool\": {\n" +
  3. " \"should\": [\n" +
  4. " { \"match\": { \"name\": \"?0\"}},\n" +
  5. " { \"match\": { \"title\": \"?0\"}},\n" +
  6. " { \"match\": { \"description\": \"?0\"}},\n" +
  7. " { \"match\": { \"category_name\": \"?0\"}}\n" +
  8. " ]\n" +
  9. " }\n" +
  10. "}")
  11. // 上面指定查询语句的情况下,方法的方法名就可以随意起名了,参数对应查询语句中的"?0"
  12. Iterable<SpuForElastic> querySearch(String keyword);

测试代码

  1. @Test
  2. void getSpuByQuery(){
  3. // 调用查询四个字段包含指定关键字数据的方法
  4. Iterable<SpuForElastic> spus=
  5. spuRepository.querySearch("华为手机");
  6. spus.forEach(spu -> System.out.println(spu));
  7. }

拓展

在实际开发中

我们数据库中的数据和Elasticsearch中的数据还存在同步问题

为了保持数据库中的数据和Elasticsearch中的数据一致

我们可以使用下面的办法

1.在所有对spu表进行增删改的操作代码运行后,也对ES中的数据进行相同的操作

​ 但是会有比较多的代码要编写,而且有比较明显的事务处理问题

  1. 实际上业界使用Elasticsearch有一个组合叫ELK,其中L(logstash)可以实现自动同步数据库和ES的信息

实际运行查询的逻辑是需要分页的

所以要按照SpringData支持的分页查询格式修改上面的查询代码

  1. @Query("{\n" +
  2. " \"bool\": {\n" +
  3. " \"should\": [\n" +
  4. " { \"match\": { \"name\": \"?0\"}},\n" +
  5. " { \"match\": { \"title\": \"?0\"}},\n" +
  6. " { \"match\": { \"description\": \"?0\"}},\n" +
  7. " { \"match\": { \"category_name\": \"?0\"}}\n" +
  8. " ]\n" +
  9. " }\n" +
  10. "}")
  11. // 上面指定查询语句的情况下,方法的方法名就可以随意起名了,参数对应查询语句中的"?0"
  12. //↓↓↓↓ ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
  13. Page<SpuForElastic> querySearch(String keyword, Pageable pageable);

修改了方法的定义,原有的调用会报错,注释掉测试中的调用代码即可!

开发搜索功能的业务逻辑层

SearchServiceImpl类添加实现方法如下

  1. // 根据指定关键字分页查询ES中商品信息的方法
  2. @Override
  3. public JsonPage<SpuForElastic> search(
  4. String keyword, Integer page, Integer pageSize) {
  5. // 根据参数中的分页数据,执行分页查询,注意SpringData分页页码从0开始
  6. Page<SpuForElastic> spus=spuRepository.querySearch(
  7. keyword, PageRequest.of(page-1,pageSize));
  8. // 当前业务逻辑层返回值是JsonPage类型,但是我们SpringData查询返回Page类型
  9. // 我们需要将Page类型对象转换为JsonPage返回
  10. // 可以在JsonPage类中编写一个专门转换的方法,也可以直接在当前方法中转换
  11. JsonPage<SpuForElastic> jsonPage=new JsonPage<>();
  12. // 分页信息
  13. jsonPage.setPage(page);
  14. jsonPage.setPageSize(pageSize);
  15. jsonPage.setTotal(spus.getTotalElements());
  16. jsonPage.setTotalPage(spus.getTotalPages());
  17. // 分页数据
  18. jsonPage.setList(spus.getContent());
  19. // 别忘了返回!!!
  20. return jsonPage;
  21. }

开发控制层代码

创建controller包

包中创建SearchController编写搜索方法,代码如下

  1. @RestController
  2. @RequestMapping("/search")
  3. @Api(tags = "搜索模块")
  4. public class SearchController {
  5. @Autowired
  6. private ISearchService searchService;
  7. // 搜索功能设计的路径为: localhost:10008/search
  8. // 因为搜索模块功能少,路径可以尽量简练
  9. // @GetMapping后面什么都不写,就表示采用类上声明的/search路径即可
  10. @GetMapping
  11. @ApiOperation("根据用户输入的关键字分页查询商品信息")
  12. @ApiImplicitParams({
  13. @ApiImplicitParam(value = "搜索关键字",name = "keyword",example = "手机"),
  14. @ApiImplicitParam(value = "页码",name = "page",example = "1"),
  15. @ApiImplicitParam(value = "每页条数",name = "pageSize",example = "2"),
  16. })
  17. public JsonResult<JsonPage<SpuForElastic>> searchByKeyword(
  18. String keyword,Integer page,Integer pageSize){
  19. JsonPage<SpuForElastic> jsonPage=
  20. searchService.search(keyword, page, pageSize);
  21. return JsonResult.ok(jsonPage);
  22. }
  23. }

测试

保证Nacos\seata\ES启动

因为当前search项目过滤器解析JWT所以需要登录才能访问

启动search模块

建议启动passport模块去进行登录获得jwt

复制JWT后,粘贴到search模块的全局参数,再测试运行search模块

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/421631
推荐阅读
相关标签
  

闽ICP备14008679号