關於樹結構的查詢優化,及許可權樹的查詢優化

靈湖映北辰發表於2021-01-01

在有限的經驗下,主要討論以下兩部分內容

1.對樹形資料結構的查詢,且層級較少,採用新增分級欄位的方式優化
2.針對前端使用ztree ,list直接返回結果集,存在授權問題的查詢,採用新增層次碼的方案

1.針對需要樹結構的查詢
  • 在有限的多級目錄情況下,可以新增分級欄位,(1級目錄,2級目錄,3級目錄),
  • 1.在這個基礎上假設只有三級目錄,那麼我們可以直接分3次查詢,此時得到了3個關於1、 2、 3層級對應的list;
  • 2.上一步的操作,使我們極大的改善了資料庫查詢的效率,只需要3次查詢即可拿到所有的資料,但同樣,需要解決拼樹的問題
  • 3.對樹的拼接,在目前大部分伺服器使用多核的情況下,推薦使用java8 Stream操作對原始list進行加工。

基於以上假設,單條資料如下,即正常查詢list中資料泛型為Regions

		@Data
		public class Regions {
		    private Integer objectId;//id
		    private String regionName;//區域名稱
		    private String regionCode;//區域編碼
		    private Integer level;//區域級別
		    private String parentCode;//上級區域編號
		}

返回到頁面的物件

@Data
	public class TreeNode {
	    private String regionName;
	    private String regionCode;
	    private String parentCode;
	    List<TreeNode> children;
	}

演算法寫法

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.toList;

@Service
public class RegionServiceImpl implements RegionService {
    @Resource
    private RegionsMapper regionsMapper;

    @Override
    public List<TreeNode> getRegionTree() {
    	//根據層級關係查詢了3次
        List<Regions> level1 = regionsMapper.getListByLevel(1);
        List<Regions> level2 = regionsMapper.getListByLevel(2);
        List<Regions> level3 = regionsMapper.getListByLevel(3);
        //先將最末級轉為TreeNode 的形式
        List<TreeNode> node3 = level3.stream().map(l3 -> {
            TreeNode treeNode = new TreeNode();
            treeNode.setRegionCode(l3.getRegionCode());
            treeNode.setRegionName(l3.getRegionName());
            treeNode.setParentCode(l3.getParentCode());
            //沒有下級children 不做處理
            return treeNode;
        }).collect(toList());

        //對二級目錄進行抽象
        List<TreeNode> node2 = level2.stream().map(a -> {
            TreeNode treeNode = new TreeNode();
            treeNode.setRegionName(a.getRegionName());
            treeNode.setRegionCode(a.getRegionCode());
            treeNode.setParentCode(a.getParentCode());
            treeNode.setChildren(node3.stream().filter(l3 -> l3.getParentCode().equals(a.getRegionCode())).collect(toList()));
            return treeNode;
        }).collect(toList());
        //對一級目錄繼續抽象並返回
        List<TreeNode> node1 = level1.stream().map(a -> {
            TreeNode treeNode = new TreeNode();
            treeNode.setRegionName(a.getRegionName());
            treeNode.setRegionCode(a.getRegionCode());
            treeNode.setParentCode(a.getParentCode());
            treeNode.setChildren(node2.stream().filter(l2 -> l2.getParentCode().equals(a.getRegionCode())).collect(toList()));
            return treeNode;
        }).collect(toList());
        return node1;
    }
}

在這裡插入圖片描述
1000條資料,耗時1s多一點,這裡作者連的是遠端資料庫加上網速也不是很樂觀,若網路連線暢通,還會快一些。

備註 :若層級較多時,可以進一步簡化(抽象),以for迴圈的形式讀取每個層級的資料,然後以同樣以for迴圈的形式對資料進行從低到高的封裝,一個方法做迴圈層級讀取資料,另一個方法做對多個層級進行 樹的推導
2.針對zTree授權樹的查詢優化
  • 前言:
  • 前端使用zTree外掛時,可以進一步簡化後端的查詢,也不存在樹的結構化返回,實際上,如果沒有許可權的管理,我們只需要拿到所有的資料,並對資料欄位進行取別名,使其符合zTree的規範即可。

  • 當目錄層級存在許可權時,比如子目錄存在許可權,但父目錄並沒有授權,但在前端介面中,我們同樣需要載入父級目錄顯示給使用者,以維持整個目錄的結構。

  • 思考

    理論上,我們從上往下進行遞迴,每驗證一個目錄需要驗證 它的子目錄 是否存在許可權,如果存在許可權,那麼就將這個目錄存放到list中,並繼續遞迴它的下級目錄
    弊端 :當資料量較大時,其需要不斷查詢下級目錄,並不斷做驗證許可權的查詢,驗證 本級及下級是否存在許可權 理論上也需要做遞迴,這樣就導致了大量的查詢操作,使效率極大的被限制。

  • 解決方案

    加入層次碼(同樣是不可重複的)欄位,即 比如上級選單的層次碼為 xxx ,那麼下級選單的層次碼 必定為xxxyyy 或xxxzzz,並且它的多層子目錄的層次碼必定為xxx開頭。
    基於以上的假設,我們的思路如下:

    • 1.查詢到所有的已授權目錄的層次碼,並標識為這樣的一個List 集合:List<Authority> authList Authority 主要欄位為 ccm(層次碼)

    • 2.查詢所有的目錄,即不管是否存在授權都進行查詢 ,存放於:List<Menu> menus ,Menu的主要欄位是業務欄位,及ccm 欄位,注意menus 是可以直接返回到前端的一個list,我們現在要做的只是篩選授權的資料,並返回即可

    • 3.使用java 8 Stream 方法,根據層次碼進行篩選,程式碼如下:

    public List<Menus> getAuthroityMenus(){
        List<Menus> menus = new ArrayList<>();//假設這裡查詢了資料庫,獲得了所有的目錄
        List<Authority> authList = new ArrayList<>();//假設這裡查詢了資料庫,得到了所有授權的目錄
        
       return menus.stream().filter(a->{
            //a,表示每一個menu物件
            //同樣的,下方的b 對應每一個Authority物件
            //遍歷授權目錄,如果 授權目錄的ccm   以此刻的Menu.ccm 開頭,則表示這個menu是已授權的上級,
            if (authList.stream().filter(b->b.getCcm().startsWith(a.getCcm())).count()>0){
                return true;
            }
            return false;
        }).collect(Collectors.toList());
        
    }

java8 的寫法有不瞭解的可以搜下相關資料,博主自己也寫過一些java 8相關的內容,這裡就不做具體的細節介紹了。
有疑問或有其他更好的方法,也希望大家在評論區積極發言!

相關文章