up:: SpringBoot电商项目商品分类模块后台的分类列表平铺接口
说明:
(1) 本篇博客的核心是就一条:递归查询的解决套路;主要有两点:
● 由于接口文档,要求是返回的数据,需要是递归嵌套的数据;所以,要创建CategoryVO这个bean,去包装查到的数据;以满足接口对返回数据的格式的要求;
● 递归查询的逻辑;(这条尤为重要);
(2) 本篇博客内容其实不难,但可能存在一定的理解难度;静下心来,仔细看,就很容易理解的;
一:先看下接口文档和接口说明
(1)前台的【分类列表(递归)】接口:在前台页面上的显示效果;
可以看到,我们这儿的【商品分类】需要按照层级关系,递归的查询出来;
(2)前台的【分类列表(递归)】接口:接口文档;
因为,这个接口是前台的,所以这个接口不需要是会员(包括管理员)登录;即,这个接口的url是【/category/list】,没有admin;
该接口返回的内容:
{
"status": 10000,
"msg": "SUCCESS",
"data": [
{
"id": 3,
"name": "新鲜水果",
"type": 1,
"parentId": 0,
"orderNum": 1,
"childCategory": [
{
"id": 4,
"name": "橘子橙子",
"type": 2,
"parentId": 3,
"orderNum": 1,
"childCategory": [
{
"id": 19,
"name": "果冻橙",
"type": 3,
"parentId": 4,
"orderNum": 1,
"childCategory": []
}
]
},
{
"id": 11,
"name": "草莓",
"type": 2,
"parentId": 3,
"orderNum": 2,
"childCategory": []
},
{
"id": 12,
"name": "奇异果",
"type": 2,
"parentId": 3,
"orderNum": 3,
"childCategory": []
},
{
"id": 14,
"name": "车厘子",
"type": 2,
"parentId": 3,
"orderNum": 4,
"childCategory": []
},
{
"id": 28,
"name": "其他水果",
"type": 2,
"parentId": 3,
"orderNum": 4,
"childCategory": []
}
]
},
{
"id": 5,
"name": "海鲜水产",
"type": 1,
"parentId": 0,
"orderNum": 2,
"childCategory": [
{
"id": 7,
"name": "螃蟹",
"type": 2,
"parentId": 5,
"orderNum": 1,
"childCategory": []
},
{
"id": 8,
"name": "鱼类",
"type": 2,
"parentId": 5,
"orderNum": 2,
"childCategory": []
},
{
"id": 13,
"name": "海参",
"type": 2,
"parentId": 5,
"orderNum": 3,
"childCategory": []
}
]
},
{
"id": 6,
"name": "精选肉类",
"type": 1,
"parentId": 0,
"orderNum": 3,
"childCategory": [
{
"id": 16,
"name": "牛羊肉",
"type": 2,
"parentId": 6,
"orderNum": 1,
"childCategory": []
}
]
},
{
"id": 9,
"name": "冷饮冻食",
"type": 1,
"parentId": 0,
"orderNum": 4,
"childCategory": [
{
"id": 17,
"name": "冰淇淋",
"type": 2,
"parentId": 9,
"orderNum": 1,
"childCategory": []
}
]
},
{
"id": 10,
"name": "蔬菜蛋品",
"type": 1,
"parentId": 0,
"orderNum": 5,
"childCategory": [
{
"id": 18,
"name": "蔬菜综合",
"type": 2,
"parentId": 10,
"orderNum": 1,
"childCategory": []
}
]
},
{
"id": 27,
"name": "美味菌菇",
"type": 1,
"parentId": 0,
"orderNum": 7,
"childCategory": [
{
"id": 15,
"name": "火锅食材",
"type": 2,
"parentId": 27,
"orderNum": 5,
"childCategory": []
}
]
}
]
}
接口返回中,data数据分析:
所以,这就要求:我们在查询某个一级目录时,要查查该一级目录下是否有所属的二级目录;然后如果有二级目录的话,要查查该二级目录下是否有所属的三级目录;
这在接口文档中,定义了规范:
(3)接口文档分析:结果;(递归数据的分析;创建,承载递归数据的实体Bean:CategoryVO)
通过上面的分析,可以得到下面的逻辑分析:【接口文档中,规定了后端返回的数据格式】→【其数据格式,明显体现了递归的思想】→【后端为了能够组织出,这样的、充满递归思想的、符合接口要求的、数据格式:需要创建一种充满递归思想的实体Bean】→【因为要符合接口要求,在这个实体Bean中,应需要有个属性是List集合;;然后,该集合中是数据类型也是该实体Bean】→【所以,这儿我们就创建了CategoryVO这个实体Bean】;
CategoryVO:
package com.imooc.mall.model.vo;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class CategoryVO {
private Integer id;
private String name;
private Integer type;
private Integer parentId;
private Integer orderNum;
private Date createTime;
private Date updateTime;
private List<CategoryVO> childCategory = new ArrayList<>();
public List<CategoryVO> getChildCategory() {
return childCategory;
}
public void setChildCategory(List<CategoryVO> childCategory) {
this.childCategory = childCategory;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public Integer getOrderNum() {
return orderNum;
}
public void setOrderNum(Integer orderNum) {
this.orderNum = orderNum;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
}
能理解【List<CategoryVO> childCategory】在递归查询中的,层层嵌套所起的作用;很重要;
二:正式开发;
1.在CategoryController类中,创建前台商品分类目录的方法:listCategoryForCustomer()方法;
/**
* 前台的,分类目录列表
* @return
*/
@ApiOperation("前台分类目录列表")
@GetMapping("/category/list")
@ResponseBody
public ApiRestResponse listCategoryForCustomer() {
List<CategoryVO> categoryVOS = categoryService.listCategoryForCustomer();
return ApiRestResponse.success(categoryVOS);
}
说明:
(1) url,请求方式,要符合接口要求;
(2) 因为,这个接口就是查询所有的商品分类数据;即,这个接口的目标是固定且明确的;所以,这个接口不需要参数;
(3) 因为要符合接口的要求:接口的返回结果的ApiRestResponse的data,就是一个List<CategoryVO>;
(4) 有关Service层的listCategoryForCustomer()方法,在下一部分介绍;
2.在CategoryServiceImpl实现类中,编写分页查询的方法listCategoryForCustomer();然后在CategoryService接口中,反向生成方法的声明;
(1)在CategoryServiceImpl实现类中,编写分页查询的方法listCategoryForCustomer();(递归查询:核心,重点!!!)
/**
* 递归查询得到,分类目录数据;(针对前台的)
* @return
*/
@Override
public List<CategoryVO> listCategoryForCustomer() {
//定义一个List,这个List就用来存在最终的查询结果;即,这个List中的直接元素是:所有的parent_id=0,即type=1的,第1级别的目录;
List<CategoryVO> categoryVOList = new ArrayList<CategoryVO>();
//我们额外创建recursivelyFindCategories()方法,去实现递归查询的逻辑;
//我们第一次递归查询时,是先查一级目录;(而一级目录的parentId是0)
//该方法第一个参数是:List<CategoryVO> categoryVOList:用来存放当前级别对应的,所有的下一级目录数据;
// PS:对于【最终返回给前端的List<CategoryVO> categoryVOList】来说,其所谓的下一级目录就是:所有的parent_id=0,即type=1的,第1级别的目录;
// PS:对于【所有的parent_id=0,即type=1的,第1级别的目录;】来说,其categoryVOList就是【List<CategoryVO> childCategory属性】,其是用来存放该级别对应的所有的parent_id=1,即type=2的,第2级别的目录;
// PS:对于【所有的parent_id=1,即type=2的,第2级别的目录;】来说,其categoryVOList就是【List<CategoryVO> childCategory属性】,其是用来存放该级别对应的所有的parent_id=2,即type=3的,第3级别的目录;
//该方法的第二个参数是:当前级别目录的parent_id,即也就是当前级别的上一级目录的id;
//即,第一个参数是【上一级别的List<CategoryVO> categoryVOList】;第二参数是【下一级别的parent_id,也就是当前级别的id】;
recursivelyFindCategories(categoryVOList, 0);
return categoryVOList;
}
/**
* 递归查询分类目录数据的,具体逻辑;;;其实就是,递归获取所有目录分类和子目录分类,并组合称为一个“目录树”;
* @param categoryVOList :存放所有下级别分类目录的数据;
* @param parentId :某级分类目录的parentId;
*/
private void recursivelyFindCategories(List<CategoryVO> categoryVOList, Integer parentId) {
//首先,根据parent_id,查询出所有该级别的数据;(比如,第一次我们查询的是parent_id=0,即type=1的,第1级别的目录)
List<Category> categoryList = categoryMapper.selectCategoriesByParentId(parentId);
//然后,遍历上面查询的该级别的数据;去尝试查询该级别数据的,下一级别的数据;
if (!CollectionUtils.isEmpty(categoryList)) {
//遍历所有查到的当前级别数据,把其放在对应上级目录的【List<CategoryVO> categoryVOList】中;
for (int i = 0; i < categoryList.size(); i++) {
//获取到【上面查询的,该级别数据中的,一条数据】,把其存储到上级目录的List<CategoryVO> childCategory属性中;
//自然,如果该级别是【parent_id=0,即type=1的,第1级别的目录】,就是把其存储在最顶级的、返回给前端的那个List<CategoryVO> categoryVOS中;
Category category = categoryList.get(i);
CategoryVO categoryVo = new CategoryVO();
BeanUtils.copyProperties(category, categoryVo);
categoryVOList.add(categoryVo);
//然后,这一步是关键:针对【每一个当前级别的,目录数据】去递归调用recursivelyFindCategories()方法;
//自然,第一个参数是【当前级别数据的,List<CategoryVO> childCategory属性】:这是存放所有下级别目录数据的;
//第二个参数是【当前级别数据的id】:这自然是下级别目录数据的parent_id:
recursivelyFindCategories(categoryVo.getChildCategory(), categoryVo.getId());
}
}
}
说明:
(1)这儿并不是很好理解(但其实还好啦),要仔细看注释;主要是递归查询的思想;以及,每个CategoryVO中的【List <CategoryVO> childCategory】是用来存放,所有对应下级目录数据的;
而这种,对应有关联的级别之间的层层包裹,就是我们这儿递归的核心;
(2) Dao层的selectCategoriesByParentId()方法,是根据parent_id查询对应级别分类数据;具体内容,在下一部分介绍;
(3) 这儿的核心是,熟悉【解决递归查询需求的,策略】;
(2)然后在CategoryService接口中,反向生成方法的声明;
3.在CategoryMapper接口中,定义【根据parent_id查询对应级别分类数据】的方法selectCategoriesByParentId();然后在CategoryMapper.xml中编写方法的SQL;
(1)在CategoryMapper接口中,定义【根据parent_id查询对应级别分类数据】的方法selectCategoriesByParentId();
这儿为了更加“完美”,虽然只有一个参数,也使用了@Param注解;
(2)然后在CategoryMapper.xml中编写方法的SQL;
<select id="selectCategoriesByParentId" parameterType="int" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"></include>
from imooc_mall_category
where parent_id = #{parentId}
</select>