我们要想完成高效的搜索任务,需要ES的支持
因为数据库的模糊查询效率太低了
我们就是说,我们再前端页面中完成的搜索是从ES中搜索数据
这样就要求,我们再查询之前,需要先将商品信息(spu)保存到ES中
准备实体类我们搜索功能编写在mall-search模块中
找到cn.tedu.mall.pojo.search.entity下的SpuforElastic
这个类有四个需要分词的属性
//SPU名称
@Field(name = "name",type = FieldType.Text,
analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
@ApiModelProperty(value="SPU名称")
private String name;
//.....
//标题
@Field(name="title",type = FieldType.Text,
analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
@ApiModelProperty(value="标题")
private String title;
// 简介
@Field(name="description",type = FieldType.Text,
analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
@ApiModelProperty(value="简介")
private String description;
//.....
//类别名称(冗余)
@Field(name="category_name",type = FieldType.Text,
analyzer = "ik_max_word",searchAnalyzer = "ik_max_word")
@ApiModelProperty(value="类别名称(冗余)")
private String categoryName;
//.....
创建ES的持久层
连接ES的持久层的包名repository(SpringData的规范名称)
创建SpuForElasticRepository代码如下
public interface SpuForElasticRepository extends
ElasticsearchRepository<SpuForElastic,Long> {
}
这个接口提供了批量新增到ES数据的方法
但是要想获得数据库中的所有pms_spu表的数据,必须连接数据库查询这些数据
但是search模块是负责管理ES的,所以需要Dubbo调用Product模块获取所有数据
Product模块查询所有数据product查询所有spu的持久层代码SpuMapper
// 全查所有spu
@Select("select * from pms_spu")
List<Spu> findAllList();
业务逻辑层用分页进行查询
ForFrontSpuServiceImpl添加方法
@Override
public JsonPage<Spu> getSpuByPage(Integer pageNum, Integer pageSize) {
PageHelper.startPage(pageNum,pageSize);
List<Spu> list=spuMapper.findAllList();
return jsonPage.restPage(new PageInfo<>(list));
}
因为一般加载到ES中的数据量非常大(几十万上百万条),
我们不可能一次性将所有数据查询出来,增到ES中,必须分批分次
分页查询就是典型的分批查询,每次查询一部分数据,通过循环遍历,将每页数据都新增到ES中
Search模块执行加载mall-search模块 创建service.impl包
包中创建ServiceLocalServiceImpl类,用于本地ES新增和使用
业务逻辑层代码如下
@Service
@Slf4j
public class ServiceLocalServiceImpl implements ISearchService {
@DubboReference // Dubbo消费模块
private IForFrontSpuService dubboSpuService;
@Autowired
private SpuForElasticRepository spuRepository;
// 利用Dubbo从product模块中分页查询所有spu数据,并新增到ES的操作
@Override
public void loadSpuByPage() {
// 先要查询一次,才能知道分页信息,制定循环条件,是典型的先运行后判断
// 所以推荐大家使用do-while循环
int i=1; //循环次数,也是页码
int pages=0; // 总页数初始值为0即可
do{
// Dubbo查询当前页spu信息
JsonPage<Spu> spus=dubboSpuService.getSpuByPage(i,2);
// 实例化一个SpuForElastic泛型的集合,以备spus集合中的数据转换后赋值添加
List<SpuForElastic> esSpus=new ArrayList<>();
// 遍历从数据库中查询出的spus
for(Spu spu:spus.getList()){
// 实例化一个ES的实体类
SpuForElastic esSpu=new SpuForElastic();
// 将spu对象的同名属性赋值给esSpu
BeanUtils.copyProperties(spu,esSpu);
// 将赋好值的esSpu对象,新增到esSpus集合中
esSpus.add(esSpu);
}
// 利用SpringData框架提供的批量新增方法执行新增操作
spuRepository.saveAll(esSpus);
pages=spus.getTotalPage();
log.info("成功新增第{}页数据",i);
i ;
}while (i<=pages);// 循环条件是当前页码不超过总页数
}
@Override
public JsonPage<SpuEntity> search(String keyword, Integer page, Integer pageSize) {
return null;
}
}
下面进行测试
@SpringBootTest
public class TestSearch {
@Autowired
private ISearchService searchService;
@Test
void loadData(){
searchService.loadSpuByPage();
System.out.println("ok");
}
}
运行测试前保证
Nacos\Seata\\ES
Leaf\product\
运行测试,没有报错即可
验证数据加载我们可以继续在测试类中编写全查当前ES的方法
遍历输出
验证当前数据加载成功
@Autowired
private SpuForElasticRepository elasticRepository;
@Test
void getAll(){
Iterable<SpuForElastic> es=elasticRepository.findAll();
es.forEach(e-> System.out.println(e));
}
测试自定义条件查询
上次课我们已经将数据库中的spu保存到了ES中,并能够全查出来
我们现在可以在Repository接口中新增自定义方法查询包含"手机"的商品信息
SpuForElasticRepository
@Repository
public interface SpuForElasticRepository extends
ElasticsearchRepository<SpuForElastic,Long> {
// 自定义查询方法
Iterable<SpuForElastic> querySpuForElasticsByTitleMatches(String title);
}
测试类添加方法测试
@Test
void getSpu(){
Iterable<SpuForElastic> it=elasticRepository
.querySpuForElasticsByTitleMatches("手机");
it.forEach(e-> System.out.println(e));
}
保证Nacos\Redis\ES启动
不需要其他项目,直接启动测试即可
下面完成多条件查询
SpringData也支持我们再代码中编写查询语句
@Query("{\n"
" \"bool\": {\n"
" \"should\": [\n"
" { \"match\": { \"name\": \"?0\"}},\n"
" { \"match\": { \"title\": \"?0\"}},\n"
" { \"match\": { \"description\": \"?0\"}},\n"
" { \"match\": { \"category_name\": \"?0\"}}\n"
" ]\n"
" }\n"
"}")
Iterable<SpuForElastic> querySearch(String keyword);
测试代码
@Test
void getKeyword(){
Iterable<SpuForElastic> it=elasticRepository.querySearch("手机");
it.forEach(e-> System.out.println(e));
}
再实际开发中
我们ES数据还有更新问题
所有对Spu数据库表进行增删改操作时,都需要将操作同步到ES
也就是在业务逻辑层中,更新完spu表后,还要更新ES,这会有很多问题,不但业务复杂,而且还有事务问题
我们会在后面学习Linux系统中有更好的解决办法
最后添加支持分页查询的持久层方法
@Query("{\n"
" \"bool\": {\n"
" \"should\": [\n"
" { \"match\": { \"name\": \"?0\"}},\n"
" { \"match\": { \"title\": \"?0\"}},\n"
" { \"match\": { \"description\": \"?0\"}},\n"
" { \"match\": { \"category_name\": \"?0\"}}\n"
" ]\n"
" }\n"
"}")
Page<SpuForElastic> querySearch(String keyword, Pageable pageable);
开发搜索功能的业务逻辑层
首先修改一下原有的业务逻辑层接口返回值的泛型
ISearchService
public interface ISearchService {
// ES分页查询spu的方法
// ↓↓↓↓↓↓↓↓↓↓↓↓↓
JsonPage<SpuForElastic> search(String keyword, Integer page, Integer pageSize)
//......
}
ServiceLocalServiceImpl实现类添加方法
@Override
public JsonPage<SpuForElastic> search(String keyword, Integer page, Integer pageSize) {
// SpringData分页,参数0表示第一页,需要将page-1才能查询正确页码
Page<SpuForElastic> spus=spuRepository.querySearch(
keyword, PageRequest.of(page-1,pageSize));
// 业务逻辑层要求返回JsonPage类型,我们目前最简单的办法就是实例化JsonPage对象给它赋值
JsonPage<SpuForElastic> jsonPage=new JsonPage<>();
// 当前页码
jsonPage.setPage(page);
jsonPage.setPageSize(pageSize);
// 赋值总页数
jsonPage.setTotalPage(spus.getTotalPages());
// 赋值总条数
jsonPage.setTotal(spus.getTotalElements());
// 赋值分页数据
jsonPage.setList(spus.getContent());
// 别忘了返回JsonPage!!!
return jsonPage;
}
开发控制层代码
创建controller包
包中创建搜索方法,代码如下
@RestController
@RequestMapping("/search")
@Api(tags = "搜索模块")
public class SearchController {
@Autowired
private ISearchService searchService;
// 搜索模块最主要的功能就是实现搜索
// 面对这种功能对应的控制器方法,可以不写任何路径
// 按下面@GetMapping注解表示当前控制路径为localhost:10008/search
@GetMapping
@ApiOperation("根据关键字查询ES中的信息")
@ApiImplicitParams({
@ApiImplicitParam(value = "搜索关键字",name = "keyword",dataType = "string",
required = true),
@ApiImplicitParam(value = "页码",name = "page",dataType = "int",
required = true),
@ApiImplicitParam(value = "每页条数",name = "pageSize",dataType = "int",
required = true)
})
public JsonResult<JsonPage<SpuForElastic>> searchByKeyword(
String keyword,
@RequestParam(value = "page",defaultValue = "1")Integer page,
@RequestParam(value = "pageSize",defaultValue = "5") Integer pageSize
){
JsonPage<SpuForElastic> list=searchService.search(keyword,page,pageSize);
return JsonResult.ok(list);
}
}
继续保证Nacos\Redis\ES正在运行
启动seata
依次启动相关服务(因为代码没有对搜索功能放行,所以必须登录才能访问,如果需要生成JWT,还需启动passport)
Leaf\Product\search\passport
按登录流程先访问10002获得jwt
再测试http://localhost:10008/doc.html
全局参数设置完成后测试搜索
学习记录,如有侵权请联系删除