MySQL树状结构表查询通解

news2024/11/23 13:09:52

文章目录

  • 前言
  • 一、数据准备
  • 二、代码实现
  • 三、案例使用
    • 1. 建立数据表实体类
    • 2. mapper文件
    • 3. 使用
  • 四、总结

前言

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

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

  • Oracle数据库中存在树查询语句,可以直接查询出某个节点的所有父节点或子节点。

  • MySQL数据库中并没有Oracle数据库中的树查询语句,其解决思路大致可分为两种:

    • 方法一:将数据库中所有的数据一次性全部查询出来,而后在代码层面获取节点的父、子节点
    • 方法二:在代码层面以某个节点的唯一标识在代码层面进行递归,每次递归从数据库中查询树父、子节点

    在MySQL中方法二由于与数据库进行了多次交互,而与数据库的交互所花费的时间要远远大于代码层面所花费的时间,方法一与数据库只进行一次交互,因此该方法效率比方法二高。

​ 笔者基于方法一对该方法提高了该方法的复用性。方法一在解决树状查询时,通常是仅仅针对特定的一张表而言的,无法通用的解决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<T>类与常用的树状节点功能相同,记录当前节点及其直接子节点。主要关键点是使用到了泛型,从而保证了本方法的通用性。

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<>();
    }

    /**
     * 根据制定字段建立树和对应后代节点集合
     * @param list 所有节点
     * @param head 对象类型
     * @param fatherFiled 父节点字段
     * @param sonFiled 当前节点字段
     * @param start 起始字段的值
     */
    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);//建树
        }
    }

    /**
     * 根据指定节点获得该节点的所有子节点
     * @param fatherMap 父节点唯一标识
     * @param startNode 起始节点
     * @param sonFiled 当前节点唯一标识属性
     * @param head 类
     * @return
     */
    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;
    }

    /**
     * 根据起始节点获得所有父节点
     * @param sonMap 节点集
     * @param startNode 起始节点
     * @param head 类
     * @param fatherFiled 父节点唯一标识属性
     * @return
     */
    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;
    }

    /**
     * 根据起始节点复现树状结构
     * @param fatherMap 父节点集
     * @param startNode 起始节点
     * @param head 类
     * @param sonFiled 当前节点唯一标识属性
     * @return
     */
    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<T>类是本方法功能的主要实现类,在使用时只需调用buildListAndTree()方法,需传入5个参数。

  • 第一个参数:数据表中所有有效节点数据
  • 第二个参数:数据表实体类的Class类
  • 第三个参数:实体类中表示父节点字段的属性名(即是上面数据表中parent_id、后续实体类中parentId属性)
  • 第四个参数:实体类中表示当前节点唯一标识字段的属性名(即是上面数据表中id、后续实体类中id属性)
  • 第五个参数:起始节点的唯一标识(即是起始节点的id值)
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
@AllArgsConstructor
public class MyNode {
    private Integer id;
    private Integer parentId;
    private String content;
}

2. mapper文件

@Mapper
public interface MyNodeMapper extends BaseMapper<MyNode> {
}

3. 使用

@SpringBootTest
class 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中所有含有树状结构的表均可直接使用,只需建立对应的实体类,以及明确实体类中表示节点间父子关系的属性名

四、总结

  • 本方法所用到的技术点并不是那么高深,所包含的只是Java的基本内容预计基础的数据结构。如:Java中的泛型、反射,数据结构中的队列、多叉树等基础知识。
    • 泛型的使用使得本方法对任何数据表都具有复用性
    • 反射的使用能够根据动态获取实体类对象的指定属性值
    • 多叉树是本方法中构建树状结构的关键数据结构
    • 在构建树状结构时队列的使用使得构建效率得到了提升
  • 本方法虽然可以解决树状查询的问题,但还存在一些问题,如:在处理大量数据时,存在内存溢出问题
  • 除技术上的总结外,其它非编码能力的提升也很大
    • 在实际开发中要善于发现重复性的工作,并对该工作进行抽象,进而得到一个通用性的解
    • 代码能力需要不断的实践,这个实践并不是说不断做大量重复性的工作,要将所学到的东西付诸实际开发中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/589474.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

这是我见过对redis最直白的讲解,看完我哭了......

我是Redis 你好&#xff0c;我是Redis&#xff0c;一个叫Antirez的男人把我带到了这个世界上。 说起我的诞生&#xff0c;跟关系数据库MySQL还挺有渊源的。 在我还没来到这个世界上的时候&#xff0c;MySQL过的很辛苦&#xff0c;互联网发展的越来越快&#xff0c;它容纳的数…

0801向量及其线性运算-向量代数与空间解析几何

1 向量的概念 既有大小又有方向的量叫做向量&#xff08;或矢量&#xff09;。 表示 用有向线段表示向量&#xff1a; 向量大小&#xff1a;有向线段的长度向量方向&#xff1a;有向线段的方向 示例如下图1-1所示&#xff1a; 记法&#xff1a;字母上面加箭头 单字母&#x…

9.前后端的身份认证

1 web开发模式 开发模式有两种&#xff0c;一种是服务端直接返回html字符串&#xff0c;另一种是前后端分类 服务端直接返回html字符串是这样的 这样做的优缺点如下 优点 加载页面快&#xff0c;这样会让设备的能耗减小&#xff0c;对于移动端来讲更省电利于搜索引擎获取数…

北京筑龙吴英礼:ChatGPT在采购与招标中的应用

近日&#xff0c;由中国招标投标协会举办的“人工智能对招标采购数字化发展的机遇与挑战交流座谈会”在北京召开。北京筑龙CTO吴英礼受邀出席&#xff0c;围绕"ChatGPT在招标投标中的应用"进行探讨&#xff0c;从ChatGPT背后的的技术、ChatGPT助力招标投标行业数字化…

【C++】-8- string〔常见接口函数使用〕

文章目录 概览标准库中的string类「string类&#xff08;了解&#xff09;」﹝编码﹞ 「string类的常用函数接口」﹝1.string类对象的常见构造﹞﹝2.string类对象的修改操作﹞‹ c_str ›‹ npos ›‹ string结构 › ﹝3.string类对象的容量操作﹞‹ clear ›‹ reserve ›‹ r…

Pulsar 负载均衡与transaction_coordinator_assign

背景与现状 TC加载到哪个broker上取决于transaction_coordinator_assign-partition-${TC ID}分区加载到哪个broker上。 默认transaction_coordinator_assign有16个分区&#xff0c;因此默认有16个TC&#xff0c;我们需要根据集群机器/broker数目来设置合理的TC个数。 为了保证…

如何使用递归 Grep 命令在目录中搜索?

在 Linux 系统中&#xff0c;grep 是一个强大的文本搜索工具&#xff0c;可以用于在文件中查找指定的文本模式。当需要在目录及其子目录中搜索特定的文本时&#xff0c;可以使用递归 grep 命令来快速定位目标文件。本文将详细介绍如何使用递归 grep 命令来搜索目录中的文件。 递…

如何搭建第一个SpringBoot+MyBatis项目

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开兴好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

ChatGPT已能模仿任何写作风格,让你的自媒体快速起号

我认识的一两个技术大佬目前失业在家&#xff0c;压力不小。对于现在的就业市场来说&#xff0c;再找工作&#xff0c;高不成低不就。他们的薪资&#xff0c;一般企业无法承受&#xff0c;大厂岗位又在缩减。今年真正感受到了寒冬。 对于我们还有饭吃的程序员&#xff0c;现在不…

【Linux网络服务】Apache网页优化

Apache网页优化 一、网页压缩1.1网页压缩步骤 二、网页缓存三、隐藏版本信息五、Apache防盗链 一、网页压缩 在企业中&#xff0c;部署Apache后只采用默认的配置参数&#xff0c;会引发网站很多问题&#xff0c;换言之默认配置是针对以前较低的服务器配置的&#xff0c;以前的…

如何通过自学成为一名白帽黑客(网安工程师)

从事网络安全这么多年&#xff0c;总是会被问到很多奇奇怪怪的问题&#xff1a; 「叔叔&#xff0c;我Steam账号被盗了&#xff0c;能帮忙找回吗&#xff1f;我给你发红包」 「我的手机被监控了&#xff0c;生活和工作受到了严重影响&#xff0c;该怎么解决&#xff1f;」 「…

学会这两件事,让你在人生路上走得更远

人生&#xff0c;就是一场不断前行&#xff0c;没有退路的旅行&#xff0c;也是一场不断醒悟的过程。 看透&#xff0c;然后醒悟&#xff1b;放下&#xff0c;然后幸福。 有些事&#xff0c;看淡就好&#xff1b;有些人&#xff0c;看穿就行。 不管世事如何艰难&#xff0c;只要…

Compose也能开发iOS了,快来体验~

前言 在之前&#xff0c;我们已经体验了Compose for Desktop 与 Compose for Web&#xff0c;目前Compose for iOS 已经有尚未开放的实验性API&#xff0c;乐观估计今年年底将会发布Compose for iOS。同时Kotlin也表示将在2023年发布KMM的稳定版本。 届时Compose-jb KMM 将实…

腾讯云4核8G服务器12M带宽支持多少人访问?

腾讯云轻量4核8G12M服务器配置446元一年&#xff0c;518元12个月&#xff0c;腾讯云轻量应用服务器具有100%CPU性能&#xff0c;系统盘为180GB SSD盘&#xff0c;12M带宽下载速度1536KB/秒&#xff0c;月流量2000GB&#xff0c;折合每天66.6GB流量&#xff0c;超出月流量包的流…

解锁接口关联测试新技能!HttpRunner教你如何轻松搞定。

目录 前言&#xff1a; 一、安装HttpRunner 二、编写测试用例 三、运行测试用例 四、实现接口关联测试 五、总结 前言&#xff1a; 在接口自动化测试中&#xff0c;一个常见的场景就是需要对多个接口进行关联测试&#xff0c;例如登录后获取token&#xff0c;再利用token…

如何自学黑客?零基础自学黑客需要多久?

问题一&#xff1a;黑客如何学起&#xff1f; 必须从学习者的角度来看&#xff0c;如果你是一个已经学过编程&#xff0c;通晓几门语言的人那么这个答案就会和一个从没有接触过的计算机&#xff0c;甚至连什么叫高级语言还不知道的人有所区别的对待。 这就像是登珠穆朗玛峰一…

ARM实验5-流水灯仿真实验

一、实验名称&#xff1a;流水灯仿真实验 二、实验目的&#xff1a; 掌握ARM处理器的输入输出接口。掌握通过MDK提供的仿真功能&#xff0c;实现系统的仿真运行。通过该编程实验&#xff0c;进一步巩固和强化学生ARM汇编编程的能&#xff0c;ARM应用程序框架&#xff0c;培养…

chatgpt赋能python:Python中的主函数调用其它函数

Python中的主函数调用其它函数 Python语言是一种高级编程语言&#xff0c;它被广泛应用于大数据处理、人工智能、数据分析、网络编程以及Web开发等领域中。在Python中&#xff0c;我们可以使用函数来封装复杂的业务逻辑&#xff0c;使代码更加可读、可维护和可扩展。在本文中&…

基于docker部署testlink并集成mantis

使用docker pull命令拉取需要的镜像。由于testlink和mantis都需要存储相关数据&#xff0c;所以这里可以看到还拉取了一个mysql镜像。 # docker pull bitnami/testlink:1.9.16-r8 # docker pull vimagick/mantisbt # docker pull mysql:5.7.20 使用docker network命令中创建…

Flutter重构开发

最近学习了flutter技术&#xff0c;然后用flutter技术重构了线上项目的首页板块&#xff0c;较深入的理解flutter的状态管理和ui组件的使用&#xff0c;总结下遇到的几点问题。 - 使用gex的controller报错 Don’t use one refreshController to multiple SmartRefresher,It w…