摘要:需求背景在很多場合,我們需要對表中的數(shù)據(jù)對遞歸查詢。結果查詢結果將會以對象返回,若有多條父依賴,將顯示在的嵌套中。雖然在查詢時,一次性獲得了級聯(lián)結構,后續(xù)解析仍然復雜。故長度最短為如果入棧次數(shù)太多,表明可能出現(xiàn)環(huán)形依賴。
1. 需求背景
在很多場合,我們需要對表中的數(shù)據(jù)對遞歸查詢。如以下情況:
1. 菜單分類中,我們往往需要由一級菜單獲得對應的子菜單。
id | name | pid |
---|---|---|
1 | 圖書 | 0 |
2 | 服裝 | 0 |
3 | 兒童讀物 | 1 |
4 | 雜志 | 1 |
5 | 卡通 | 3 |
6 | 睡前故事 | 3 |
我們希望得到的結果為,由圖書可以查到:
{ 圖書: [ 雜志, {兒童讀物:[卡通,睡前故事]} ] }
2. 在類似上述具有依賴關系的查詢時,我們將父子依賴關系抽象成以下字段:
col | col_parent |
---|---|
value1 | value2 |
value2 | value3 |
由col中的value1查詢到col_parent值為value2;
再由value2作為col查詢,查詢到value3。
注:父依賴或子依賴只是稱呼不同而已。
value1的所有父依賴
value1-->value2-->value3
value2的所有父依賴
value2-->value3
2. 實現(xiàn)方案針對上述問題,本文提出兩種解決方案。
方法1:使用mybatis中的collection標簽
優(yōu)點:框架已經封裝好了,不用深入實現(xiàn)原理。
缺點:返回結果的結構固定,不能靈活處理。對結構的解析也較復雜。擴展性差
方法2:只需根據(jù)一條查詢語句select col_parent from table_name where col=#{col}及自己的代碼即可
優(yōu)點:靈活性高。返回結構可擴展
難點:需要理解實現(xiàn)原理。
demo說明
對于mysql中如下數(shù)據(jù)表結構:
id | code | parent_code |
---|---|---|
... | ... | ... |
目標:我們要求通過code找到左右父code(包括祖輩code)
2.1 方法1:mybatis中collection標簽實現(xiàn) 查詢代碼實現(xiàn)核心代碼(其他實現(xiàn)代碼不做展示)
dao
@Mapper public interface RelationTreeDao { ListselectAllParentsByCode(String code);
dto
public class RelationTreeDTO { private String code; private String parentCode; private ListparentCodeList; //List嵌套本身,裝父節(jié)點信息 // getter and setter }
mapper.xml
說明:
RelationTreeDTO作為查詢結果的映射對象,其中需要定義自嵌套的List
mapper中的select也為簡單查詢,但是其映射結果resultMap中有collection標簽。會將column="parent_code"再作為參數(shù)#{code}循環(huán)查詢。
結果:
relationTreeDao.selectAllParentsByCode("yourCode");查詢結果將會以RelationTreeDTO對象返回,若有多條父依賴,將顯示在List的嵌套中。
[ { "code": ***, "parentCode": ***, "parentCodeList": [ { "code": ***, "parentCode": ***, "parentCodeList": [] }, ... ] } ]結果解析
對于上述結果,我們往往需要進一步獲取有用信息。如只需要一個List:
[code, parentCode, parentCode, parentCode,...]
由于RelationTreeDTO是一個樹結構,這就涉及到樹的遍歷。在此,以樹的深度優(yōu)先搜索算法,獲得上述list。
/** * description:深度優(yōu)先搜索DTO中的所有父節(jié)點 * @author wanghongbing whbing1991@gmail.com * @param treeDTO RelationTreeDTO待解析的樹結構對象 * @return list [0]存code, [1]開始存parents * 一定會有一個父節(jié)點,list長度>=2 */ @Override public ListdepthFirst(RelationTreeDTO treeDTO) { //list [0]存code, [1]開始存parents List list = new ArrayList<>(); list.add(treeDTO.getCode()); //list[0] ArrayDeque stack = new ArrayDeque(); stack.push(treeDTO); while (!stack.isEmpty()){ RelationTreeDTO node =stack.pop(); list.add(node.getParentCode()); //獲取嵌套節(jié)點 List parents = node.getParentCodeList(); if(parents !=null && parents.size() >0){ for (int i = parents.size()-1; i >=0 ; i--) { stack.push(parents.get(i)); } } } return list; }
至此,該方式級聯(lián)查詢結束。
上述實現(xiàn),collection結果為固定的樹結構,在解析時,要使用算法(如DFS)獲取樹種的節(jié)點信息。雖然在mapper查詢時,一次性獲得了級聯(lián)結構,后續(xù)解析仍然復雜。下面介紹推薦方式。
dao
@Mapper public interface RelationDao { ListselectParentByCode(String code); // 其他表 List selectOtherParentByCode(String code); }
dto(或entity,如數(shù)據(jù)庫對應的話)
public class TaskRelationDTO { private String code; private String parentCode; // getter and setter }
mapper.xml(假設有relation表和other_relation表,其字段相同。兩個表完全為了展示擴展)
說明:上述查詢僅為最簡單的sql查詢,我們將遞歸查詢寫在業(yè)務方法中。
service
/** * * @param code 待查找父任務的子任務 * @return 返回的list.size()>=2 list[0]當前code,[1]以后去重后的無序parentsCode * 如:[tag-depend-2, path-depend-0-p, path-depend-2, tag-depend-0, path-depend-0] */ @Override public ListgetAllParentsByCode(String code) { List list = new ArrayList<>(); Set parentSet = new HashSet<>(); ArrayDeque stack = new ArrayDeque(); int count = 0; final int MAX_LOOP_COUNT = 50; // 初始化stack,將code放入stack stack.push(code); // 將code加入list。如果最終list.isEmpty(),表明沒有父節(jié)點,將其清空。故list長度最短為2 list.add(code); while (!stack.isEmpty()){ // 如果入棧次數(shù)太多,表明可能出現(xiàn)環(huán)形依賴。強行終止 if(count++ > MAX_LOOP_COUNT){ LOGGER.error("code為["+code+"]任務其父任務超過"+MAX_LOOP_COUNT+"個,請檢查是否有環(huán)形依賴"); list.addAll(parentSet); // 僅有taskCode,無parentCode時,將list清空 if(list.size() == 1){ list.clear(); } return list; } String childCode = stack.pop(); /** 可能會出現(xiàn)兩個表交叉依賴情況,故有otherRelation */ List relation =relationDao.selectTagParentByCode(childCode); List otherRelation =relationDao.selectOtherParentByCode(childCode); // 從relation表中查找pop()元素的父任務,將其加入stack if(!relation.isEmpty()){ for (int i = 0; i < relation.size(); i++) { String parent = relation.get(i).getParentCode(); //這個parent是需要的,同時要將其放入stack parentSet.add(parent); stack.push(parent); } } // 從otherRelation表中查找pop()元素的父任務,將其加入stack if(!otherRelation.isEmpty()){ for (int i = 0; i < otherRelation.size(); i++) { String parent = otherRelation.get(i).getParentCode(); //這個parent是需要的,同時要將其放入stack parentSet.add(parent); stack.push(parent); } } } list.addAll(parentSet); // 僅有taskCode,無parentCode時,將list清空 if(list.size() == 1){ list.clear(); } return list; }
原理
說明:上述原理,使用棧(遞歸亦可,所謂遞歸,無非進棧出棧)來循環(huán)查詢。初始化時,將待查詢的code入棧,第一次查詢時,該code出棧,作為參數(shù)查詢,如有查詢結果(一條或多條),將查詢到的結果進棧(放入棧中,下次循環(huán)計就可以取出作為參數(shù)輸入了!)。
如上述進行了兩個表的查詢,靈活。
mybatis中的collection標簽,不推薦使用。本人在項目實踐中已由該方式更改為方式2。
循環(huán)將查詢到的結果parentCode作為code再次查詢,看似復雜,理解原理就簡單。
棧的使用。可以代替遞歸,思路清晰。
上述代碼為項目代碼,已經去除敏感信息。可能不能直接運行。如不能解決問題,可聯(lián)系代碼中郵箱。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76873.html
一:字典表 字典信息:在項目中可能會使用到,已經存在的一些信息。 例如,客戶的級別:普通用戶,vip用戶... 客戶的來源:網絡營銷,電話營銷... 客戶所屬行業(yè):電子商務,房地產... 客戶的性別:男,女 在保存用戶的時候,這些信息都是已經存在的,不應該讓用戶讓用戶來任意填寫, 而是通過下拉列表來讓用戶選擇。 這些已經存在的信息稱之為字典信...
一:字典表 字典信息:在項目中可能會使用到,已經存在的一些信息。 例如,客戶的級別:普通用戶,vip用戶... 客戶的來源:網絡營銷,電話營銷... 客戶所屬行業(yè):電子商務,房地產... 客戶的性別:男,女 在保存用戶的時候,這些信息都是已經存在的,不應該讓用戶讓用戶來任意填寫, 而是通過下拉列表來讓用戶選擇。 這些已經存在的信息稱之為字典信...
摘要:從零開始實現(xiàn)一個級聯(lián)組件本文實現(xiàn)級聯(lián)組件需要用到自定義指令和組件通信相關知識,最好先閱讀以下兩篇文章自定義指令組件基礎與通信一組件簡介本文實現(xiàn)的是一個省市縣多級聯(lián)動組件,當組件渲染完成后默認會加載出所有的省名稱,當用戶點擊某個省的名稱后,右 從零開始實現(xiàn)一個Vue級聯(lián)組件 本文實現(xiàn)級聯(lián)組件需要用到自定義指令和組件通信相關知識,最好先閱讀以下兩篇文章: Vue自定義指令 Vue組件基礎與...
閱讀 3356·2023-04-26 03:05
閱讀 1466·2019-08-30 13:09
閱讀 1914·2019-08-30 13:05
閱讀 893·2019-08-29 12:42
閱讀 1390·2019-08-28 18:18
閱讀 3451·2019-08-28 18:09
閱讀 521·2019-08-28 18:00
閱讀 1720·2019-08-26 12:10