文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

MySQL树状结构表查询通解

2023-08-19 09:25

关注

前言

​ 最近做了一个中医药方面的项目,该项目分为游戏端和服务端。笔者负责的是服务端的开发。在服务端的业务中包含两部分:系统信息管理模块、游戏端服务提供模块。由于中医药存在很多树状结构信息,因此在设计数据表时为了减少冗余度,就将很多数据表设计为了树状结构。树状结构的表能够更加有效的将数据进行管理,但在某些业务中存在查询某个节点所有子节点后父节点的需求,进而造成了查询效率低下。并且对于不同数据表而言,其字段均不相同,代码并不能复用。使得在开发过程中,存在大量重复性工作。笔者想,既然数据表都存在树状结构是否能写一个东西,能够对所有树状结构的数据表都能适用,并提高其查询效率。经过构思找到了一个实现思路。

​ 在很多项目中都存在树状结构的数据库,以表示数据间的关联和层次关系。这种数据库能够高效的描述数据间的关系,而且可以无限延申树状结构的深度。该树状结构的思想能够在数据库层面高效的解决数据存储问题,但在业务处理层面并不能高效的解决节点间父子节点的关系。因此,产生了数据库树状查询问题。对于不同的数据库有不同的解决方式:

​ 笔者基于方法一对该方法提高了该方法的复用性。方法一在解决树状查询时,通常是仅仅针对特定的一张表而言的,无法通用的解决MySQL树状查询问题,本方法在其基础上通用的解决了MySQL树状查询问题。即是凡是存在树状结构的数据表均可使用本方式进行树状查询。


一、数据准备

CREATE table arborescence (id int PRIMARY KEY,parent_id int,content VARCHAR(200));INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (1, NULL, '节点1');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (2, 1, '节点2');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (3, 1, '节点3');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (4, 2, '节点4');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (5, 2, '节点5');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (6, 2, '节点6');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (7, 3, '节点7');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (8, 3, '节点8');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (9, 3, '节点9');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (10, 4, '节点10');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (11, 6, '节点11');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (12, 7, '节点12');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (13, 8, '节点13');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (14, 8, '节点14');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (15, 9, '节点15');INSERT INTO `arborescence`(`id`, `parent_id`, `content`) VALUES (16, 15, '节点16');

在该数据表中id为每个数据记录的唯一标识,parent_id为每个数据记录的父级记录。

以上数据的树状结构关系如图:

在这里插入图片描述


二、代码实现

在本方法中主要由两个类实现。

public class Tree <T>{    T node;//当前节点    List<Tree<T>> nextNode;//子节点    public Tree(T node, List<Tree<T>> nextNode) {//有参构造        this.node = node;        this.nextNode = nextNode;    }    public Tree() {//无参构造    }    public Tree(T node) {        this.node = node;        this.nextNode=new ArrayList<>();    }    public T getNode() {        return node;    }    public void setNode(T node) {        this.node = node;    }    public List<Tree<T>> getNextNode() {        return nextNode;    }    public void setNextNode(List<Tree<T>> nextNode) {        this.nextNode = nextNode;    }}

​ Tree类与常用的树状节点功能相同,记录当前节点及其直接子节点。主要关键点是使用到了泛型,从而保证了本方法的通用性。

public class TreeUtil <T>{    public List<T> sonList,fatherList;//所有子节点、所有父节点    public Tree<T> tree;//树    public List<T> getSonList() {        return sonList;    }    public void setSonList(List<T> sonList) {        this.sonList = sonList;    }    public List<T> getFatherList() {        return fatherList;    }    public void setFatherList(List<T> fatherList) {        this.fatherList = fatherList;    }    public Tree<T> getTree() {        return tree;    }    public void setTree(Tree<T> tree) {        this.tree = tree;    }    public TreeUtil() {        this.sonList = new ArrayList<>();        this.fatherList=new ArrayList<>();        this.tree=new Tree<>();    }        public void buildListAndTree(List<T> list,Class head,String fatherFiled,String sonFiled,String start) {//根据某个节点建树和集合        Map<String,T> sonMap=new HashMap<>();//以自身唯一标识建map        Map<String,List<T>> fatherMap=new HashMap<>();//以父节点唯一标识建map        for (T temp:list) {            try {                Field field1 = head.getDeclaredField(sonFiled);//自身唯一标识                field1.setAccessible(true);                Object o1 = field1.get(temp);                sonMap.put(o1.toString(),temp);                Field field2 = head.getDeclaredField(fatherFiled);//父节点唯一标识                field2.setAccessible(true);                Object o2 = field2.get(temp);                if (o2==null) continue;//该节点为根节点不存在父节点                if (fatherMap.containsKey(o2.toString())) {//已经含有该父节点                    List<T> ts = fatherMap.get(o2.toString());                    ts.add(temp);                }else {//不含该父节点                    List<T> tempList=new ArrayList<>();                    tempList.add(temp);                    fatherMap.put(o2.toString(),tempList);                }            } catch (Exception e) {                e.printStackTrace();            }        }        if (sonMap.containsKey(start)) {            T startNode = sonMap.get(start);//起始节点            this.sonList = buildSonList(fatherMap,startNode,sonFiled,head);//建子节点            this.fatherList=buildFatherList(sonMap,startNode,head,fatherFiled);//建父节点            this.tree=buildTree(fatherMap,startNode,head,sonFiled);//建树        }    }        private List<T> buildSonList(Map<String,List<T>> fatherMap, T startNode, String sonFiled, Class head){//建集合        List<T> result=new ArrayList<>();        Queue<T> queue=new LinkedList<>();        queue.add(startNode);//队列        while (!queue.isEmpty()) {            T curNode = queue.poll();            try {                Field field = head.getDeclaredField(sonFiled);                field.setAccessible(true);                Object o = field.get(curNode);                if (fatherMap.containsKey(o.toString())) {                    List<T> sons = fatherMap.get(o.toString());//当前节点所有子节点                    queue.addAll(sons);                }                result.add(curNode);            } catch (Exception e) {                e.printStackTrace();            }        }        return result;    }        private List<T> buildFatherList(Map<String,T> sonMap,T startNode,Class head,String fatherFiled){        List<T> result=new ArrayList<>();        try {            Field field = head.getDeclaredField(fatherFiled);            field.setAccessible(true);            while (field.get(startNode)!=null) {                result.add(startNode);                startNode=sonMap.get(field.get(startNode));            }            result.add(startNode);        } catch (Exception e) {            e.printStackTrace();        }        Collections.reverse(result);        return result;    }        private Tree<T> buildTree(Map<String,List<T>> fatherMap,T startNode, Class head,String sonFiled){//建树        try {            Field field = head.getDeclaredField(sonFiled);//当前节点唯一标识            field.setAccessible(true);            Object o = field.get(startNode);            if (fatherMap.containsKey(o.toString())) {//存在子节点                List<T> sons = fatherMap.get(o.toString());                List<Tree<T>> treeSon=new ArrayList<>();                for (T son:sons) {                    Tree<T> temp = buildTree(fatherMap, son, head, sonFiled);                    treeSon.add(temp);                }                Tree<T> root=new Tree<>(startNode,treeSon);                return root;            }else {//不存在子节点                return new Tree<T>(startNode,null);            }        } catch (Exception e) {            e.printStackTrace();        }        return null;    }}

​ TreeUtil类是本方法功能的主要实现类,在使用时只需调用buildListAndTree()方法,需传入5个参数。

public void buildListAndTree(List<T> list,Class head,String fatherFiled,String sonFiled,String start) {//根据某个节点建树和集合        Map<String,T> sonMap=new HashMap<>();//以自身唯一标识建map        Map<String,List<T>> fatherMap=new HashMap<>();//以父节点唯一标识建map        for (T temp:list) {            try {                Field field1 = head.getDeclaredField(sonFiled);//自身唯一标识                field1.setAccessible(true);                Object o1 = field1.get(temp);                sonMap.put(o1.toString(),temp);                Field field2 = head.getDeclaredField(fatherFiled);//父节点唯一标识                field2.setAccessible(true);                Object o2 = field2.get(temp);                if (o2==null) continue;//该节点为根节点不存在父节点                if (fatherMap.containsKey(o2.toString())) {//已经含有该父节点                    List<T> ts = fatherMap.get(o2.toString());                    ts.add(temp);                }else {//不含该父节点                    List<T> tempList=new ArrayList<>();                    tempList.add(temp);                    fatherMap.put(o2.toString(),tempList);                }            } catch (Exception e) {                e.printStackTrace();            }        }        if (sonMap.containsKey(start)) {            T startNode = sonMap.get(start);//起始节点            this.sonList = buildSonList(fatherMap,startNode,sonFiled,head);//建子节点            this.fatherList=buildFatherList(sonMap,startNode,head,fatherFiled);//建父节点            this.tree=buildTree(fatherMap,startNode,head,sonFiled);//建树        }    }

三、案例使用

以上面数据库中数据为例,所使用的时mybatis-plus框架:

1. 建立数据表实体类

@Data@NoArgsConstructor@AllArgsConstructorpublic class MyNode {    private Integer id;    private Integer parentId;    private String content;}

2. mapper文件

@Mapperpublic interface MyNodeMapper extends BaseMapper<MyNode> {}

3. 使用

@SpringBootTestclass DemoApplicationTests {    @Resource    public MyNodeMapper myNodeMapper;    @Test    void contextLoads() {        List<MyNode> list = myNodeMapper.selectList(null);//查询出数据表中所有有效数据        TreeUtil<MyNode> util1=new TreeUtil<>();        util1.buildListAndTree(list,MyNode.class,"parentId","id","1");//以节点唯一标识为1的节点为起始节点        System.out.println("------------------------以1为起始节点------------------------");        System.out.println("************父节点************");        for (MyNode myNode:util1.getFatherList()) System.out.println(myNode.toString());        System.out.println("************子节点************");        for (MyNode myNode:util1.getSonList()) System.out.println(myNode.toString());        TreeUtil<MyNode> util2=new TreeUtil<>();        util2.buildListAndTree(list,MyNode.class,"parentId","id","3");//以节点唯一标识为3的节点为起始节点        System.out.println("------------------------以3为起始节点------------------------");        System.out.println("************父节点************");        for (MyNode myNode:util2.getFatherList()) System.out.println(myNode.toString());        System.out.println("************子节点************");        for (MyNode myNode:util2.getSonList()) System.out.println(myNode.toString());        TreeUtil<MyNode> util3=new TreeUtil<>();        util3.buildListAndTree(list,MyNode.class,"parentId","id","16");//以节点唯一标识为16的节点为起始节点        System.out.println("------------------------以16为起始节点------------------------");        System.out.println("************父节点************");        for (MyNode myNode:util3.getFatherList()) System.out.println(myNode.toString());        System.out.println("************子节点************");        for (MyNode myNode:util3.getSonList()) System.out.println(myNode.toString());    }}

结果:

------------------------以1为起始节点------------------------************父节点************MyNode(id=1, parentId=null, content=节点1)************子节点************MyNode(id=1, parentId=null, content=节点1)MyNode(id=2, parentId=1, content=节点2)MyNode(id=3, parentId=1, content=节点3)MyNode(id=4, parentId=2, content=节点4)MyNode(id=5, parentId=2, content=节点5)MyNode(id=6, parentId=2, content=节点6)MyNode(id=7, parentId=3, content=节点7)MyNode(id=8, parentId=3, content=节点8)MyNode(id=9, parentId=3, content=节点9)MyNode(id=10, parentId=4, content=节点10)MyNode(id=11, parentId=6, content=节点11)MyNode(id=12, parentId=7, content=节点12)MyNode(id=13, parentId=8, content=节点13)MyNode(id=14, parentId=8, content=节点14)MyNode(id=15, parentId=9, content=节点15)MyNode(id=16, parentId=15, content=节点16)------------------------以3为起始节点------------------------************父节点************MyNode(id=1, parentId=null, content=节点1)MyNode(id=3, parentId=1, content=节点3)************子节点************MyNode(id=3, parentId=1, content=节点3)MyNode(id=7, parentId=3, content=节点7)MyNode(id=8, parentId=3, content=节点8)MyNode(id=9, parentId=3, content=节点9)MyNode(id=12, parentId=7, content=节点12)MyNode(id=13, parentId=8, content=节点13)MyNode(id=14, parentId=8, content=节点14)MyNode(id=15, parentId=9, content=节点15)MyNode(id=16, parentId=15, content=节点16)------------------------以16为起始节点------------------------************父节点************MyNode(id=1, parentId=null, content=节点1)MyNode(id=3, parentId=1, content=节点3)MyNode(id=9, parentId=3, content=节点9)MyNode(id=15, parentId=9, content=节点15)MyNode(id=16, parentId=15, content=节点16)************子节点************MyNode(id=16, parentId=15, content=节点16)

使用本方法后,对于MySQL中所有含有树状结构的表均可直接使用,只需建立对应的实体类,以及明确实体类中表示节点间父子关系的属性名

四、总结

来源地址:https://blog.csdn.net/qq_51763048/article/details/129268313

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-数据库
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯