問題背景
項目開發中有一個樹形數據結構,不像經典組織結構樹、菜單級別樹,我們這個樹形結構是用戶后期手動建立起來的關系。因此數據庫表結構為兩張表:數據記錄表、記錄關系表,通過業務規則限制,形成的樹形結構像下面這樣:
特殊之處就是樹結構節點是有重復的
項目要求前端展示、導出時使用復制重復節點的方式。開搞吧
Mybatis樹結構查詢
樹結構查詢,在mysql下當然是使用Mybatis框架提供的遞歸查詢了。
- xml配置文件
<resultMap type="(...).OKRAlignTreeNode" id="TreeNodeResult">
<result property="id" column="objective_id" />
<result property="content" column="content" />
<result property="theOrder" column="the_order" />
<collection property="children" select="getChildren" column="objective_id" ofType="(...).OKRAlignTreeNode"/>
</resultMap>
<select id="getTree" parameterType="Map" resultMap="TreeNodeResult">
select
objective_id,content,the_order
from okr_objective oo
where oo.objective_id = #{id}
order by the_order
</select>
<select id="getChildren" resultMap="TreeNodeResult">
select objective_id,content,the_order
from
(select objective_id from okr_aline where parent_ids = #{objective_id} ) a
left join okr_objective oo on a.objective_id = oo.objective_id
order by b.the_order
</select>
- mapper文件
public interface OKRAlignExportMapper {
TreeNode getTree(Long objectiveId);
}
- 查詢結果
樹結構導出到Excel
關於樹形結構數據導出,我參考這篇博客,並針對OKR的特點做了修改。
OKR對齊視圖數據結構的特點是:
- 1.以本人的目標為中心,向左右兩側發散。
- 2.左側是自己對齊的目標,以及對齊目標再次對齊的目標,遞歸到頂。
- 3.右側是向自己對齊的目標,遞歸到底。
關於OKR對齊視圖這種數據結構的導出,我們下篇博客會把完整的代碼放上來,並分析一下。這里說一下導出這種樹形結構數據的主要步驟:
- 1.計算每條數據的行列坐標,這里采用遞歸的算法,最終可以計算出父級節點需要合並的行數,以及Excel文件的最大列數。
- 2.根據行列坐標遞歸輸出每條數據的值到Excel單元格。
Mybatis一級緩存導致的問題
首先我們來了解一下Mybatis一級緩存:
Mybatis對緩存提供支持,但是在沒有配置的默認情況下,它只開啟一級緩存,一級緩存只是相對於同一個SqlSession而言。所以在參數和SQL完全一樣的情況下,我們使用同一個SqlSession對象調用一個Mapper方法,往往只執行一次SQL,因為使用SelSession第一次查詢后,MyBatis會將其放在緩存中,以后再查詢的時候,如果沒有聲明需要刷新,並且緩存沒有超時的情況下,SqlSession都會取出當前緩存的數據,而不會再次發送SQL到數據庫。
由於Mybatis的緩存機制,導致在出現重復的葉子節點時,雖然樹結構正常構建,但是指向的是同一個java對象。因為是使用的Mybatis的遞歸查詢,因此確認整個查詢在一個SqlSession中執行完成,肯定是一級緩存導致的。這樣會造成的后果,就是無法設置重復葉子節點的正確位置,因為指向同一個java對象,后遍歷到的節點設置會覆蓋前面的節點設置。
解決方案
既然確定是一級緩存導致的,那關閉或者清除一級緩存就行了吧。因為是框架的遞歸查詢,因此無法
調用SqlSession的修改、添加、刪除、commit(),close等
清空一級緩存。那怎么辦呢,笨辦法了:
既然樹的結構關系時正確的,只是重復節點指向了同一個java對象,那就遍歷重建對象吧
/**
* 深度拷貝樹結構
* @param node
* @return
*/
private static OKRAlignTreeNode deepCopyTree(OKRAlignTreeNode node){
OKRAlignTreeNode newNode = node.clone();
List<OKRAlignTreeNode> children = node.getChildren();
if(children!=null&&children.size()>0){
List<OKRAlignTreeNode> newChildren = new LinkedList<>();
for (OKRAlignTreeNode child:children){
if(child!=null){
OKRAlignTreeNode newChild = deepCopyTree(child);
newChildren.add(newChild);
}
}
newNode.setChildren(newChildren);
}
return newNode;
}