欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java?處理樹形結(jié)構(gòu)數(shù)據(jù)的過程

 更新時間:2022年08月01日 15:05:37   作者:逆風(fēng)飛翔的小叔  
這篇文章主要介紹了Java?處理樹形結(jié)構(gòu)數(shù)據(jù)的過程,本文給大家分析具體實現(xiàn)過程,結(jié)合實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

前言

問題的背景大概是這樣的,有下面這樣一個excel表,原始數(shù)據(jù)結(jié)構(gòu)如下:

需求點:

  • 導(dǎo)入excel的機構(gòu)層級數(shù)據(jù)到mysql的機構(gòu)表(這里假設(shè)為 depart);
  • 導(dǎo)入的機構(gòu)數(shù)據(jù)以層級進(jìn)行保存和區(qū)分;
  • 界面展示時需要以完整的樹形層級進(jìn)行展示;

處理過程

按照上面已知的信息,設(shè)計一個簡單的機構(gòu)表

CREATE TABLE `depart` (
  `depart_id` varchar(64) DEFAULT NULL,
  `pid` varchar(64) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `path` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 

接下來分析下具體的實現(xiàn);

1、導(dǎo)入的時候以層級保存數(shù)據(jù) 

有做過樹形結(jié)構(gòu)處理業(yè)務(wù)的小伙伴們對樹形結(jié)構(gòu)的處理并不陌生,主要就是在機構(gòu)表中合理運用 id 和 pid 構(gòu)建數(shù)據(jù)層級關(guān)系,期間可能涉及到遞歸的操作(不建議在數(shù)據(jù)庫層面遞歸);

但我們說具體問題具體分析,比如按照上面的exce表的數(shù)據(jù)結(jié)構(gòu),以 “/產(chǎn)品研發(fā)中心/業(yè)務(wù)一部” 這個機構(gòu)為例做簡單說明

  • 以 “/” 區(qū)分,這條數(shù)據(jù)涉及到2個部門,“產(chǎn)品研發(fā)中心” 為一級部門,而 “業(yè)務(wù)一部” 為其子部門,即二級部門;
  • 按照第一步的分析,需要將這條數(shù)據(jù)拆開來做處理,通過部門名稱以及其所在層級(業(yè)務(wù)中可能還存在其他字段),即在查庫過程中作為查詢條件;
  • 需要考慮excel中的數(shù)據(jù)存在性,比如 “產(chǎn)品研發(fā)中心” 這條數(shù)據(jù)在數(shù)據(jù)庫中可能存在,也可能不存在,“業(yè)務(wù)一部” 也可能存在或不存在;
  • 在第三步的基礎(chǔ)上,需要按照程序的邏輯設(shè)置一定的規(guī)則,大概如下:1、頂級部門不存在,可以認(rèn)為這條數(shù)據(jù)在數(shù)據(jù)庫中不存在,處理的時候直接按照正常的規(guī)則;2、頂級部門存在,直接處理最后一級數(shù)據(jù);
  • 不考慮中間部門層級,像 “/A/B/C” 中的B是否存在問題;

2、返回樹形層級結(jié)構(gòu)數(shù)據(jù)

相比導(dǎo)入來說,返回樹形層級結(jié)構(gòu)相對來說,已經(jīng)有相對成熟的處理方式,大致思路如下:

  • 查詢所有數(shù)據(jù)(如果考慮動態(tài)加載另說);
  • 構(gòu)建返回數(shù)據(jù)的樹形結(jié)構(gòu)(可根據(jù) id 和 pid 的關(guān)系);

樹形結(jié)構(gòu)的返回對象結(jié)構(gòu)大致如下

public class DepartDTO {
    private String departId;
    private String pid;
    private String name;
    private String path;
    private String pname;
    private List<DepartDTO> children;
}

返回樹形層級數(shù)據(jù)

先來做第二個需求,由于解決思路已經(jīng)給出,只需要按照思路進(jìn)行即可,下面貼出主要的邏輯代碼

    public static String random() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
    public static List<DepartDTO> getDepartTree(List<DepartDTO> allDepart) {
        //查詢到的所有的部門數(shù)據(jù)
        //List<DepartDTO> allDepart = getAllDepart();
        //最高級別用戶集合
        List<DepartDTO> roots = new ArrayList<>();
        List<DepartDTO> res = new ArrayList<>();
        for (DepartDTO departDto : allDepart) {
            //-1表示最高級別的用戶
            if (departDto.getPid().equals("0")) {
                roots.add(departDto);
            }
        }
        //從最高級別用戶開始遍歷,遞歸找到該用戶的下級用戶,將帶有下級的最高級用戶放入返回結(jié)果中
        for (DepartDTO userDto : roots) {
            userDto = buildUserTree(allDepart, userDto);
            res.add(userDto);
        }
        return res;
    }
    public static DepartDTO buildUserTree(List<DepartDTO> allDeparts, DepartDTO departDTO) {
        List<DepartDTO> children = new ArrayList<>();
        //遍歷查到的所有用戶
        for (DepartDTO departDTO1 : allDeparts) {
            //-1代表根節(jié)點,無需重復(fù)比較
            if (departDTO1.getPid().equals("0") || departDTO1.getPname().equals("") || departDTO1.getPname() == null)
                continue;
            //當(dāng)前用戶的上級編號和傳入的用戶編號相等,表示該用戶是傳入用戶的下級用戶
            if (departDTO1.getPname().equals(departDTO.getName())) {
                //遞歸調(diào)用,再去尋找該用戶的下級用戶
                departDTO1 = buildUserTree(allDeparts, departDTO1);
                //當(dāng)前用戶是該用戶的一個下級用戶,放入children集合內(nèi)
                children.add(departDTO1);
            }
        }
        //給該用戶的children屬性賦值,并返回該用戶
        departDTO.setChildren(children);
        return departDTO;
    }

這里先直接模擬一部分的數(shù)據(jù),通過這部分的數(shù)據(jù)做處理

static List<String> departLists = new ArrayList<>();
    static {
        departLists.add("/產(chǎn)品研發(fā)中心/業(yè)務(wù)中臺部");
        departLists.add("/產(chǎn)品研發(fā)中心/技術(shù)中臺部");
        departLists.add("/產(chǎn)品研發(fā)中心/技術(shù)中臺部/產(chǎn)品A組");
        departLists.add("/總裁辦");
        departLists.add("/總裁辦/品牌管理部");
    }
    public static void main(String[] args) {
        List<DepartDTO> allDepart = getAllDepart();
        List<DepartDTO> departTree = getDepartTree(allDepart);
        System.out.println(departTree);
    }

上面程序用到的兩個工具類

public class PathUtils {
    public static List<String> getPaths(String path) {
        List<String> parentPaths = getParentPaths(path);
        parentPaths.add(path);
        return parentPaths;
    }
    public static String getParentPath(String path) {
        return StringUtils.substringBeforeLast(StringUtils.removeEnd(path, "/"), "/");
    }
    public static List<String> getParentPaths(String path) {
        List<String> paths = new ArrayList<>();
        while (true) {
            path = getParentPath(path);
            if (StringUtils.isBlank(path)) {
                break;
            }
            paths.add(path);
        }
        paths.sort(String::compareTo);
        return paths;
    }
    /**
     * 拼接部門名稱完整路徑,如 湖北省,襄陽市,谷城縣,最終組裝成 : 湖北省/襄陽市/谷城縣
     *
     * @param paths
     * @return
     */
    public static String merge(List<String> paths) {
        return merge(paths, null);
    }
    public static String merge(List<String> paths, Function<String, String> function) {
        if (CollectionUtils.isEmpty(paths)) {
            return null;
        }
        Stream<String> stream = paths.stream();
        if (Objects.nonNull(function)) {
            stream = stream.map(function);
        }
        return stream.filter(Objects::nonNull).collect(joining("/", "/", ""));
    }
    public static String getNextPath(String path) {
        path = StringUtils.removeEnd(path, "/");
        String parentPath = StringUtils.substringBeforeLast(path, "/");
        int val = NumberUtils.toInt(StringUtils.substringAfterLast(path, "/")) + 1;
        return parentPath + "/" + StringUtils.leftPad(String.valueOf(val), 4, "0");
    }
    public static String getNextPath(String parentPath, List<String> childPaths) {
        if (CollectionUtils.isEmpty(childPaths)) {
            return parentPath + "/001";
        }
        if (childPaths.size() + 1 >= 1000) {
            throw new RuntimeException("同級機構(gòu)最多支持9999個");
        }
        //獲取同級最大值路徑
        Collections.sort(childPaths, Comparator.reverseOrder());
        String maxPath = childPaths.get(0);
        if (StringUtils.isNotBlank(maxPath)) {
            return PathUtils.getNextPath(maxPath);
        }
        return parentPath + "/001";
    }
    public static void main(String[] args) {
        /*System.out.println(getParentPaths("/001/002/003/004/"));
        List<String> childPaths = new ArrayList<>();
        childPaths.add("/001");
        childPaths.add("/007");
        childPaths.add("/1000");
        childPaths.add("/001");
        childPaths.add("/901");
        childPaths.add("/766");
        List<Integer> result = new ArrayList<>();
        childPaths.forEach(item ->{
            result.add(Integer.valueOf(item.substring(1)));;
        });
        Integer max = Collections.max(result);
        System.out.println(max);*/
        String pathNames = "/產(chǎn)品研發(fā)中心/業(yè)務(wù)中臺部";
        String substring = pathNames.substring(pathNames.lastIndexOf("/") + 1);
        System.out.println(substring);
        //String paths = "/001/002/003/004/";
        String paths = "/001/001";
        List<String> parentPaths = getParentPaths(paths);
        System.out.println(parentPaths);
    }
    public static String getMaxPath(List<String> pathList) {
        List<Integer> result = new ArrayList<>();
        pathList.forEach(item -> {
            result.add(Integer.valueOf(item.substring(1)));
            ;
        });
        Integer max = Collections.max(result);
        return String.valueOf("/" + max);
    }
}

最后寫個接口模擬下

     //localhost:8087/getAllDepart
    @GetMapping("/getAllDepart")
    public Object getAllDepart() {
       
        return departService.importDepart();
    }

運行上面的main程序,觀察控制臺輸出結(jié)果

用格式化工具處理下再看,這即為我們期待的結(jié)果,實際業(yè)務(wù)中,只需要在 getAllDepart 這個方法中,將獲取數(shù)據(jù)從數(shù)據(jù)庫查詢即可;

[
    {
        "departId":"e1c6d8ba4a504b7da85472ca713be107",
        "pid":"0",
        "name":"產(chǎn)品研發(fā)中心",
        "path":null,
        "pname":"",
        "children":[
            {
                "departId":"8e39b272531449ca96c0668ae60d2c2f",
                "pid":"e1c6d8ba4a504b7da85472ca713be107",
                "name":"業(yè)務(wù)中臺部",
                "path":null,
                "pname":"產(chǎn)品研發(fā)中心",
                "children":[
 
                ]
            },
            {
                "departId":"ecfe24e1769248df885287c7e153f9e6",
                "pid":"e1c6d8ba4a504b7da85472ca713be107",
                "name":"技術(shù)中臺部",
                "path":null,
                "pname":"產(chǎn)品研發(fā)中心",
                "children":[
                    {
                        "departId":"0218c648abdf4867ad5ea1e99098d526",
                        "pid":"ecfe24e1769248df885287c7e153f9e6",
                        "name":"產(chǎn)品A組",
                        "path":null,
                        "pname":"技術(shù)中臺部",
                        "children":[
 
                        ]
                    }
                ]
            }
        ]
    },
    {
        "departId":"843bfa6b371e4d7d8d44894d939ca0a5",
        "pid":"0",
        "name":"總裁辦",
        "path":null,
        "pname":"",
        "children":[
            {
                "departId":"12dc458b6996484394e2026d5b0f547e",
                "pid":"843bfa6b371e4d7d8d44894d939ca0a5",
                "name":"品牌管理部",
                "path":null,
                "pname":"總裁辦",
                "children":[
 
                ]
            }
        ]
    }
]

數(shù)據(jù)導(dǎo)入

其實,只要按照上文的處理思路做即可,但是這里提一個在邏輯編寫過程中遇到的一個比較難處理的問題,即機構(gòu)的 path 上;

這里必須要說一下這個 path 的事情,path 在真實的業(yè)務(wù)場景中,是一個非常重要,并且在眾多的使用場景中高頻使用的字段,因為對一個機構(gòu)來說,通過業(yè)務(wù)的區(qū)分,這個path一定是唯一的存在; 

仍然使用文章開頭的那些數(shù)據(jù),最終將 “/產(chǎn)品研發(fā)中心/業(yè)務(wù)一部” 這樣的數(shù)據(jù)入庫時,需要將數(shù)據(jù)組裝成一個個對象插入到數(shù)據(jù)庫,同時,插入數(shù)據(jù)之前,層級也需要組裝好,那么對于 “/產(chǎn)品研發(fā)中心/業(yè)務(wù)一部” 這樣一條數(shù)據(jù),可以想象到,將會產(chǎn)生兩個 depart 對象,這里我們考慮下面兩個簡單的場景;

  • 如果頂級部門不存在,全量導(dǎo)入,比如 “/產(chǎn)品研發(fā)中心/業(yè)務(wù)一部” 這樣一條數(shù)據(jù),當(dāng) “/產(chǎn)品研發(fā)中心” 不存在時,完整導(dǎo)入;
  • “/產(chǎn)品研發(fā)中心/業(yè)務(wù)一部” ,當(dāng)  “產(chǎn)品研發(fā)中心” 存在時,只需導(dǎo)入 “業(yè)務(wù)一部” ;

下面來看核心代碼

@Service
public class DepartTest {
 
    @Autowired
    private DepartDao departDao;
 
    @Autowired
    private TransactionUtils transactionUtils;
 
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
 
    static List<String> departLists = new ArrayList<>();
 
    private static final String tenantId = "e10adc3949ba59abbe56e057f20f88dd";
 
    static {
        departLists.add("/top1");
        departLists.add("/top1/ch1");
        departLists.add("/top1/ch1/ch2");
 
    }
 
    public static List<String> getFullNames(String departName) {
        List<String> result = new ArrayList<>();
        String[] splitNames = departName.split("/");
        for (int i = 0; i < splitNames.length; i++) {
            if (!StringUtils.isEmpty(splitNames[i])) {
                result.add(splitNames[i]);
            }
        }
        return result;
    }
 
 
    public List<DepartDTO> getAllDepart() {
 
        List<DepartDTO> departDTOS = new ArrayList<>();
        //保存 部門名稱和部門ID的映射關(guān)系
        Map<String, String> nameDepartIdMap = new HashMap<>();
        List<Depart> dbExistDepart = new ArrayList<>();
        List<Depart> newDeparts = new ArrayList<>();
 
        for (String single : departLists) {
            //全部的部門名稱
            List<String> fullNames = getFullNames(single);
            //直接父級
            String parentPath = PathUtils.getParentPath(single);
            //處理頂級的部門數(shù)據(jù)【只有自己本身,比如 "/總裁辦"】
            if (StringUtils.isEmpty(parentPath)) {
                //1、說明當(dāng)前只有一級,即頂級數(shù)據(jù)
                //2、如果是頂級數(shù)據(jù),則需要判斷數(shù)據(jù)庫是否存在,如果已經(jīng)存在,不用管,如果不存在,生成新的相關(guān)數(shù)據(jù)
                Depart depart = departDao.getTopDepartByName(fullNames.get(0));
                if (depart != null) {
                    nameDepartIdMap.put(fullNames.get(0), depart.getDepartId());
                    //確認(rèn)數(shù)據(jù)庫已經(jīng)存在過的,后面只需要新建部門與用戶的關(guān)系即可
                    dbExistDepart.add(depart);
                    continue;
                }
                //如果數(shù)據(jù)不存在,新生成
                String departId = random();
                Depart newDepart = new Depart();
                newDepart.setDepartId(departId);
                newDepart.setName(fullNames.get(0));
                newDepart.setPid("0");
                newDepart.setPath(DepartUtils.getNextPath(tenantId, "0"));
                TransactionStatus transaction = transactionUtils.getTransaction();
                try {
                    departDao.insert(newDepart);
                    //設(shè)置手動提交事務(wù)
                    dataSourceTransactionManager.commit(transaction);
                } catch (Exception e) {
                    dataSourceTransactionManager.rollback(transaction);
                }
                newDeparts.add(newDepart);
                nameDepartIdMap.put(fullNames.get(0), departId);
                continue;
            }
 
            //如果是非頂級的,則需要拆開  /產(chǎn)品研發(fā)中心/技術(shù)中臺部/產(chǎn)品A組
            for (int i = 0; i < fullNames.size(); i++) {
                String currentDepart = fullNames.get(i);
                //遍歷的時候從頂級開始
                if (nameDepartIdMap.containsKey(currentDepart))
                    continue;
 
                if (i == 0) {
                    TransactionStatus transaction = transactionUtils.getTransaction();
                    //仍然是頂級,需要先查數(shù)據(jù)庫
                    Depart topDepart = departDao.getTopDepartByName(currentDepart);
                    if (topDepart != null) {
                        nameDepartIdMap.put(fullNames.get(0), topDepart.getDepartId());
                        dbExistDepart.add(topDepart);
                    } else {
                        //如果數(shù)據(jù)不存在,新生成
                        String departId = random();
                        Depart _depart = new Depart();
                        _depart.setDepartId(departId);
                        _depart.setName(fullNames.get(0));
                        _depart.setTenantId(tenantId);
                        _depart.setPid("0");
                        _depart.setPath(DepartUtils.getNextPath(tenantId, "0"));
                        try {
                            departDao.insert(_depart);
                            //設(shè)置手動提交事務(wù)
                            dataSourceTransactionManager.commit(transaction);
                        } catch (Exception e) {
                            dataSourceTransactionManager.rollback(transaction);
                        }
                        if (fullNames.size() == 1) {
                            newDeparts.add(_depart);
                        }
                    }
                    continue;
                }
                //處理其他層級數(shù)據(jù)
                String parentName = parentPath.substring(parentPath.lastIndexOf("/") + 1);
                //開啟一個新的事務(wù)
                TransactionStatus transaction = transactionUtils.getTransaction();
                //判斷自身是否已經(jīng)存在了
                Depart dbCurrentDepart = departDao.getDepartByNameAndPid(currentDepart, nameDepartIdMap.get(parentName));
                if (dbCurrentDepart != null) {
                    //如果已經(jīng)存在了,直接跳過
                    dbExistDepart.add(dbCurrentDepart);
                    nameDepartIdMap.put(currentDepart, dbCurrentDepart.getDepartId());
                    dataSourceTransactionManager.commit(transaction);
                    continue;
                }
 
                Depart _depart = new Depart();
                _depart.setTenantId(tenantId);
                String departId = random();
                _depart.setDepartId(departId);
                _depart.setName(currentDepart);
                String pid = nameDepartIdMap.get(parentName);
 
                //判斷上級部門在數(shù)據(jù)庫是否真的存在
                Depart directParent = departDao.getDepartById(nameDepartIdMap.get(parentName));
 
                boolean isCurrentDepartDbExist = false;
                if (directParent != null) {
                    _depart.setPid(pid);
                    String nextPath = DepartUtils.getNextPath(tenantId, pid);
                    _depart.setPath(nextPath);
                    departDao.insert(_depart);
                    dataSourceTransactionManager.commit(transaction);
                    //父級存在時
                    nameDepartIdMap.put(currentDepart, departId);
                }
 
                //如果是最后的那一個,才是本次實際要關(guān)聯(lián)的部門數(shù)據(jù)
                if (i == fullNames.size() - 1) {
                    if (isCurrentDepartDbExist) {
                        dbExistDepart.add(_depart);
                        nameDepartIdMap.put(currentDepart, departId);
                        continue;
                    }
                    newDeparts.add(_depart);
                    nameDepartIdMap.put(currentDepart, departId);
                }
            }
        }
        return departDTOS;
    }
 
 
    public static String random() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
}

在代碼編寫過程中,有一個比較難處理的問題,就是在讀取外部數(shù)據(jù),組裝depart 的path的時候,為什么這么講呢?

要知道,根據(jù)上面描述的兩種實現(xiàn)情況,path 可能需要動態(tài)組裝而成,很多同學(xué)可能會說,可以先把depart對象全部組裝完成,最后再通過層級關(guān)系構(gòu)建出 path 的完整路徑;

事實上,一開始我也是這么想的,但是最終發(fā)現(xiàn)這樣走不通,原因就在于 頂級部門 “/產(chǎn)品研發(fā)中心”在數(shù)據(jù)庫中可能存在,也可能不存在,而 path的生成一定是需要結(jié)合數(shù)據(jù)庫的某些業(yè)務(wù)字段動態(tài)查詢而構(gòu)造出來的;

所以如果先組裝完成數(shù)據(jù)再構(gòu)建path,這樣帶來的問題復(fù)雜性將會大大增加;

那么比較可行的而且可以實現(xiàn)的方式就是,在組裝數(shù)據(jù)的過程中,動態(tài)查庫進(jìn)行組裝數(shù)據(jù);

但是小編在編碼的時候發(fā)現(xiàn),如果使用springboot工程自身的事務(wù)管理器的話,無論是哪種事務(wù)隔離級別,都將無法滿足這樣一個需求,即 “前一步將父級部門數(shù)據(jù)插入,子部門能夠查到父級的數(shù)據(jù)”這樣一個問題;

所以為了達(dá)到這個目的,這里采用了 jdbc 手動管理事務(wù)的方式進(jìn)行;

那么通過上面的方式,就可以實現(xiàn)層級數(shù)據(jù)的導(dǎo)入效果了,具體邏輯可以參考注釋說明進(jìn)行理解;

到此這篇關(guān)于Java 處理樹形結(jié)構(gòu)數(shù)據(jù)的文章就介紹到這了,更多相關(guān)java 樹形結(jié)構(gòu)數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論