Mybatis一級快取的鍋

問題背景

項目開發中有一個樹形數據結構,不像經典組織結構樹、菜單級別樹,我們這個樹形結構是用戶後期手動建立起來的關係。因此資料庫表結構為兩張表:數據記錄表、記錄關係表,通過業務規則限制,形成的樹形結構像下面這樣:

特殊之處就是樹結構節點是有重複的

  • 不複製重複節點
    5NVrIU.png

  • 複製重複節點

5NVDaT.png

項目要求前端展示、導出時使用複製重複節點的方式。開搞吧

Mybatis樹結構查詢

樹結構查詢,在mysql下當然是使用Mybatis框架提供的遞歸查詢了。

  1. 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>
  1. mapper文件
public interface OKRAlignExportMapper {
    TreeNode getTree(Long objectiveId);
}
  1. 查詢結果

5NVyiF.png

樹結構導出到Excel

關於樹形結構數據導出,我參考這篇部落格,並針對OKR的特點做了修改。

Java 樹形結構數據生成導出excel文件

OKR對齊視圖數據結構的特點是:

  • 1.以本人的目標為中心,向左右兩側發散。
  • 2.左側是自己對齊的目標,以及對齊目標再次對齊的目標,遞歸到頂。
  • 3.右側是向自己對齊的目標,遞歸到底。

關於OKR對齊視圖這種數據結構的導出,我們下篇部落格會把完整的程式碼放上來,並分析一下。這裡說一下導出這種樹形結構數據的主要步驟:

  • 1.計算每條數據的行列坐標,這裡採用遞歸的演算法,最終可以計算出父級節點需要合併的行數,以及Excel文件的最大列數。
  • 2.根據行列坐標遞歸輸出每條數據的值到Excel單元格。

Mybatis一級快取導致的問題

首先我們來了解一下Mybatis一級快取:

Mybatis對快取提供支援,但是在沒有配置的默認情況下,它只開啟一級快取,一級快取只是相對於同一個SqlSession而言。所以在參數和SQL完全一樣的情況下,我們使用同一個SqlSession對象調用一個Mapper方法,往往只執行一次SQL,因為使用SelSession第一次查詢後,MyBatis會將其放在快取中,以後再查詢的時候,如果沒有聲明需要刷新,並且快取沒有超時的情況下,SqlSession都會取出當前快取的數據,而不會再次發送SQL到資料庫。

mybatis一級快取二級快取

由於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;
}