一、商品分類模塊里遞歸算法的使用場(chǎng)景
1.商品類別表的部分?jǐn)?shù)據(jù):
id parent_id name
------ --------- -----------------
100001 0 家用電器
100002 0 數(shù)碼3C
100003 0 服裝箱包
100006 100001 冰箱
100007 100001 電視
100008 100001 洗衣機(jī)
100009 100001 空調(diào)
100010 100001 電熱水器
100011 100002 電腦
100012 100002 手機(jī)
100037 100009 格力空調(diào)
100038 100009 美的空調(diào)
100039 100037 格力掛壁式空調(diào)
100040 100037 格力立柜式空調(diào)
id 字段是主鍵体谒,parent_id 字段是該類別的父節(jié)點(diǎn)的 id 〉蘖現(xiàn)在有個(gè)需求就是給一個(gè) id ,返回他自身以及他的所有子節(jié)點(diǎn)(包括子節(jié)點(diǎn)的子節(jié)點(diǎn)扎唾。召川。。胸遇。)
比如荧呐,查詢 id 為 100001(家用電器)的所有子節(jié)點(diǎn)。那么結(jié)果應(yīng)該是:
--家用電器
--冰箱
--電視
--洗衣機(jī)
--空調(diào)
--格力空調(diào)
--格力掛壁式空調(diào)
--格力立柜式空調(diào)
--美的空調(diào)
--電熱水器
這種需求轉(zhuǎn)化為代碼的話纸镊,遞歸調(diào)用是最方便的倍阐。
二、把需求轉(zhuǎn)化為代碼
1. 邏輯:
首先從數(shù)據(jù)庫(kù)中獲取 parent_id 為家用電器的數(shù)據(jù)逗威,我們得到的是一個(gè) list 集合(冰箱峰搪,電視,洗衣機(jī)凯旭,空調(diào)概耻,電熱水器)。然后對(duì) list 集合中的每一個(gè) item 進(jìn)行遍歷罐呼,在遍歷的時(shí)候去從數(shù)據(jù)庫(kù)中獲取 parent_id 為 item 的集合鞠柄,如果集合不是空的,那么再對(duì)這個(gè)集合再遍歷嫉柴。厌杜。。一直照此往復(fù)计螺。夯尽。。
我們可以從這個(gè)總的過(guò)程中抽離出一個(gè)核心的過(guò)程:
?? 按照某個(gè)條件獲取一個(gè)集合登馒,如果集合不為空匙握,對(duì)集合中的 每一項(xiàng) 再 按照原來(lái)相同的條件 獲取集合。
編寫(xiě) sql 語(yǔ)句谊娇,查詢表中 parent_id 為指定 id 的所有記錄:
<select id="selectCategoriesByParentId" resultType="top.kongk.mmall.pojo.Category">
select
<include refid="Base_Column_List" />
from mmall_category
where parent_id = #{parentId,jdbcType=INTEGER}
</select>
2. 原項(xiàng)目的代碼與不足:
原項(xiàng)目中的獲取所有子節(jié)點(diǎn)的代碼是這樣寫(xiě)的:
public ServerResponse<List<Integer>> selectCategoryAndChildrenById(Integer categoryId){
Set<Category> categorySet = Sets.newHashSet();
findChildCategory(categorySet,categoryId);
List<Integer> categoryIdList = Lists.newArrayList();
if(categoryId != null){
for(Category categoryItem : categorySet){
categoryIdList.add(categoryItem.getId());
}
}
return ServerResponse.createBySuccess(categoryIdList);
}
//遞歸算法,算出子節(jié)點(diǎn)
private Set<Category> findChildCategory(Set<Category> categorySet ,Integer categoryId){
Category category = categoryMapper.selectByPrimaryKey(categoryId);
if(category != null) {
categorySet.add(category);
}
//查找子節(jié)點(diǎn),遞歸算法一定要有一個(gè)退出的條件
List<Category> categoryList = categoryMapper.selectCategoryChildrenByParentId(categoryId);
for(Category categoryItem : categoryList){
findChildCategory(categorySet,categoryItem.getId());
}
return categorySet;
}
原項(xiàng)目的代碼有兩個(gè)不足之處:
1.本來(lái)已經(jīng)查到了 某個(gè)節(jié)點(diǎn) 封裝好的的 category 對(duì)象肺孤,遞歸的時(shí)候直接傳 category 即可罗晕。原代碼中卻只傳了 category 的 id ,然后再?gòu)臄?shù)據(jù)庫(kù)中獲取主鍵為該 id 的數(shù)據(jù)赠堵,再封裝成原來(lái)的 category小渊,多此一舉!
2.本來(lái)前臺(tái)要求的是List<Integer>茫叭,原代碼中卻用 Set<Category> 保存節(jié)點(diǎn)酬屉。最后返回前臺(tái)的時(shí)候,再新 new 一個(gè) List揍愁,遍歷 Set呐萨,把Set 里的值添加到 List中。Set 是多余的莽囤!
3. 改進(jìn):
@Override
public ServerResponse getDeepCategory(Integer categoryId) {
Category category = categoryMapper.selectByPrimaryKey(categoryId);
List<Integer> categories = new LinkedList<>();
findAllChildrenCategories(categories, category);
if (CollectionUtils.isEmpty(categories)) {
return ServerResponse.createErrorWithMsg("獲取商品類別失敗");
} else {
return ServerResponse.createSuccess(categories);
}
}
private void findAllChildrenCategories(List<Integer> categories, Category category) {
if (category != null) {
categories.add(category.getId());
} else {
return;
}
//獲取當(dāng)前 category 的第一層孩子
List<Category> categoryList = categoryMapper.selectCategoriesByParentId(category.getId());
//對(duì)第一層的每一個(gè)孩子遞歸調(diào)用此方法
for (Category categoryItem : categoryList) {
findAllChildrenCategories(categories, categoryItem);
}
}
完谬擦。