1. 程式人生 > >mysql樹形結構表設計(Path Enumerations,Closure Table)

mysql樹形結構表設計(Path Enumerations,Closure Table)

效果

首先看下返回到前臺的效果:

這裡寫圖片描述

下面是返回給前臺的json:

{
  "code": 1,
  "data": [
    {
      "children": [
        {
          "children": [
            {
              "id": 76,
              "parent_role_id": 74,
              "parentname": "管理員1號",
              "role_name": "群員1號",
              "role_type": "111"
}, { "id": 77, "parent_role_id": 74, "parentname": "管理員1號", "role_name": "群員2號", "role_type": "2222" } ]
, "id": 74, "parent_role_id": 73, "parentname": "群主"
, "role_name": "管理員1號", "role_type": "111" }, { "children": [ { "id": 78, "parent_role_id": 75, "parentname": "管理員2號", "role_name": "群員3號", "role_type": "333" } ]
, "id": 75, "parent_role_id": 73, "parentname": "群主", "role_name": "管理員2號", "role_type": "222" } ]
, "id": 73, "parent_role_id": 0, "role_name": "群主", "role_type": "111" } ]
, "msg": "執行成功" }

引言

一般比較普遍的就是四種方法:(具體見 SQL Anti-patterns這本書)
Adjacency List:每一條記錄存parent_id
Path Enumerations:每一條記錄存整個tree path經過的node列舉
Nested Sets:每一條記錄存 nleft 和 nright
Closure Table:維護一個表,所有的tree path作為記錄進行儲存。

各種方法的常用操作代價見下圖

這裡寫圖片描述

正文

如上所見,我們選擇方案2和方案4來進行設計

方案2(Path Enumerations)

這裡寫圖片描述

簡單說說,圖中的path存的是每個層級的關係,像第6條記錄的意思就是我的上級是5,5的上級是2,2的上級是1,其他記錄同理可得。

這種方法乍看很清晰很明瞭,但其實維護是比較難維護的,而且有可能會產生髒資料,畢竟你所維護的都是通過字串來維護

不做過多介紹,因為這個方案確實是最簡單的,如上所說,每種方案都適用不同業務。

方案4(Closure Table)

這個方案我也是參考上面的部落格,可以先看看他的表結構圖

這裡寫圖片描述

下面是本文的表結構

這是角色表,包含角色名稱,父角色ID
這裡寫圖片描述

這是角色關係表,ancestor_id是父級和祖父級ID,level是當前角色相對ancestor_id而言的層次,rold_id則是角色ID
這裡寫圖片描述

舉個例子,群主這個角色是屬於最頂層,那麼只插入一條關聯資料,表示自身,即 73,0,73
管理員1號是屬於群主下面的一個角色,那麼首先同樣需要先建立一個表示自身的資料,即74,0,74 ,然後建立管理員1號和群主關聯的資料,即73,1,74 這裡的1就是代表管理員1號對於群主而言,他是一級直屬關係,所以level為1

規律
每個子角色都需要和自身以及所有的上級角色關聯,假設有5個等級,A1-A2-A3-A4-A5,當建立A5的時候,先插入自身資料,然後迴圈所有上級進行關聯。

程式碼部分

public class RoleTree {
    private Integer id;
    private Integer ancestor_id;
    private Integer level;
    private Integer role_id;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public Integer getAncestor_id() {
        return ancestor_id;
    }

    public void setAncestor_id(Integer ancestor_id) {
        this.ancestor_id = ancestor_id;
    }

    public Integer getLevel() {
        return level;
    }

    public void setLevel(Integer level) {
        this.level = level;
    }

    public Integer getRole_id() {
        return role_id;
    }

    public void setRole_id(Integer role_id) {
        this.role_id = role_id;
    }
}

新增操作程式碼:

private void insertIntoRoleTreeTable(Role record) {
        //插入自身資料
        RoleTree roleTree = new RoleTree();
        roleTree.setAncestor_id(record.getId());
        roleTree.setLevel(0);
        roleTree.setRole_id(record.getId());
        roleTreeMapper.insert(roleTree);
        if (record.getParent_role_id() == 0) {
            //如果父級ID為0代表他是最根部的角色,則只插入自身資料
            return;
        }
        //插入自身和父級關聯的資料
        roleTree.setAncestor_id(record.getParent_role_id());
        roleTree.setLevel(1);
        roleTree.setRole_id(record.getId());
        roleTreeMapper.insert(roleTree);
        //查詢父級的所有上級
        List<RoleTree> roleTreeList = roleTreeMapper.selectAllParentOfRole(record.getParent_role_id());
        //迴圈父級並和新插入的資料關聯
        for (RoleTree aRoleTreeList : roleTreeList) {
            RoleTree r = new RoleTree();
            r.setRole_id(record.getId());
            r.setLevel(aRoleTreeList.getLevel() + 1);
            r.setAncestor_id(aRoleTreeList.getAncestor_id());
            roleTreeMapper.insert(r);
        }
    }
<!--查詢某個角色的所有上級-->
    <select id="selectAllParentOfRole" resultMap="BaseResultMap" parameterType="java.lang.Integer">
        select
        <include refid="Base_Column_List"/>
        from b_role_tree_relation
        where role_id = #{role_id,jdbcType=INTEGER}
        and level != 0
    </select>

    <!--查詢某個角色的所有下級-->
    <select id="selectAllChildOfRole" resultMap="BaseResultMap" parameterType="java.lang.Integer">
        select
        <include refid="Base_Column_List"/>
        from b_role_tree_relation
        where ancestor_id = #{ancestor_id,jdbcType=INTEGER}
        and level != 0
    </select>
 <!--刪除某個角色的所有關係-->
    <select id="deleteTreeRelationOfRole" parameterType="java.lang.Integer">
        delete from b_role_tree_relation where role_id in (
        select role_id from (SELECT role_id FROM b_role_tree_relation where ancestor_id= #{role_id,jdbcType=INTEGER} ) a
        )
    </select>

更新操作:
由於子角色在更新的時候,可能會從一個層級很深的角色變成根部角色或者根部的直屬角色,如果使用更新不僅要刪除原來資料,還要判斷更新資料ID,因此我的做法是直接刪除原有的所有關係,再重新插入新的關係

總結

雖然方案4處理起來較為麻煩,但是確實適用於各種業務的方案,而且他不會產生髒資料,維護以及管理時也能一目瞭然.