SpringBoot + MyBatis-Plus构建树形结构的几种方式
1. 树形结构
树形结构,是指:数据元素之间的关系像一颗树的数据结构。由树根延伸出多个树杈

它具有以下特点:
- 每个节点都只有有限个子节点或无子节点;
- 没有父节点的节点称为根节点;
- 每一个非根节点有且只有一个父节点;
- 除了根节点外,每个子节点可以分为多个不相交的子树;
- 树里面没有环路(cycle)
2. 常见问题
在实际开发中,很多数据都是树形结构,例如:地区、页面上的菜单、上下级关系的组织等等,这时就需要我们从数据源中读取到数据,通过某些方式拼成树形结构 然后再给前端展示。对于一些不经常变化且使用频繁的数据,可以考虑将拼好的树形结构数据放入缓存,每次用的时候直接读取出来就可以使用。
3. 准备环境
springboot: 2.6.0
mysql: 5.7
CREATE TABLE `t_region` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`name` varchar(100) DEFAULT NULL,`region_type` varchar(255) DEFAULT NULL,`parent_id` bigint(20) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;INSERT into t_region(name, region_type, parent_id) VALUES('山西省', 'province', 0);
INSERT into t_region(name, region_type, parent_id) VALUES('临汾市', 'city', 1);
INSERT into t_region(name, region_type, parent_id) VALUES('尧都区', 'district', 2);INSERT into t_region(name, region_type, parent_id) VALUES('北京', 'province', 0);
INSERT into t_region(name, region_type, parent_id) VALUES('北京市', 'city', 4);
INSERT into t_region(name, region_type, parent_id) VALUES('朝阳区', 'district', 5);INSERT into t_region(name, region_type, parent_id) VALUES('太原市', 'city', 1);
INSERT into t_region(name, region_type, parent_id) VALUES('小店区', 'district', 7);
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>spring-test</artifactId><version>1.0-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.0</version><relativePath/></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version><scope>provided</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.10</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- druid依赖 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.0</version></dependency></dependencies>
</project>
spring:main:allow-circular-references: truedatasource: #定义数据源#127.0.0.1为本机测试的ip,3306是mysql的端口号。serverTimezone是定义时区,照抄就好,mysql高版本需要定义这些东西#useSSL也是某些高版本mysql需要问有没有用SSL连接url: jdbc:mysql://192.168.1.141:3306/db_user?serverTimezone=GMT%2B8&useSSL=FALSEusername: root #数据库用户名,root为管理员password: 123456 #该数据库用户的密码# 使用druid数据源type: com.alibaba.druid.pool.DruidDataSource
mybatis-plus:# xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)mapper-locations: classpath:mapper/*.xml# 以下配置均有默认值,可以不设置global-config:db-config:#主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";id-type: auto#数据库类型db-type: MYSQLconfiguration:# 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射map-underscore-to-camel-case: true# 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段call-setters-on-nulls: true# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
表对应的实体类
@Data
@TableName(value = "t_region")
public class Region {@TableId(type = IdType.AUTO)private Long id;/*** 名称*/private String name;/*** 类型*/private String regionType;/*** 父id*/private Long parentId;
}
返回给前端的实体类
@Data
public class RegionVO {private Long id;/*** 名称*/private String name;/*** 类型*/private String regionType;/*** 父id*/private Long parentId;private List<RegionVO> children;
}
4.实现方式
1.基于xml
RegionMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mapper.RegionMapper"><resultMap id="regionMap" type="org.example.dto.RegionVO"><id property="id" column="id"></id><result property="name" column="name"></result><result property="regionType" column="region_type"></result><result property="parentId" column="parent_id"></result><collection property="children" ofType="org.example.dto.RegionVO" javaType="java.util.List"column="id" select="getById"></collection></resultMap><select id="getById" resultMap="regionMap" parameterType="map">SELECT*FROMt_regionwhere parent_id=#{id}</select><select id="getAll" resultMap="regionMap">SELECT*FROMt_region where parent_id = 0</select></mapper>
RegionMapper
@Mapper
public interface RegionMapper extends BaseMapper<Region> {List<RegionVO> getAll();
}
RegionService
public interface RegionService extends IService<Region> {List<RegionVO> getAll();
}
RegionServiceImpl
@Service
public class RegionServiceImpl extends ServiceImpl<RegionMapper, Region> implements RegionService {@Overridepublic List<RegionVO> getAll(){return baseMapper.getAll();}
}
这种方式按照上边添加的数据量(8条)共执行了9次查询

2.LambdaQueryWrapper
RegionServiceImpl添加如下代码
@Overridepublic List<RegionVO> getAllWrapper(){LambdaQueryWrapper<Region> wrapper = new LambdaQueryWrapper<>();wrapper.eq(Region::getParentId, 0);//查询根级List<Region> regions = baseMapper.selectList(wrapper);List<RegionVO> list = regions.stream().map(p -> {RegionVO obj = new RegionVO();BeanUtils.copyProperties(p, obj);return obj;}).collect(Collectors.toList());list.forEach(this::getChildren);return list;}private void getChildren(RegionVO item){LambdaQueryWrapper<Region> wrapper = new LambdaQueryWrapper<>();wrapper.eq(Region::getParentId, item.getId());//根据parentId查询List<Region> list = baseMapper.selectList(wrapper);List<RegionVO> voList = list.stream().map(p -> {RegionVO vo = new RegionVO();BeanUtils.copyProperties(p, vo);return vo;}).collect(Collectors.toList());//写入到childrenitem.setChildren(voList);//如果children不为空,继续往下找if (!CollectionUtils.isEmpty(voList)) {voList.forEach(this::getChildren);}}
这种方式按照上边添加的数据量(8条)共执行了9次查询

3.递归方法
RegionServiceImpl添加如下代码
@Override
public List<RegionVO> build(){//一次把所有的数据都查出来List<Region> regions = baseMapper.selectList(null);List<RegionVO> allList = regions.stream().map(p -> {RegionVO vo = new RegionVO();BeanUtils.copyProperties(p, vo);return vo;}).collect(Collectors.toList());//指定根节点的parentIdreturn buildChildren(0L, allList);
}private List<RegionVO> buildChildren(Long parentId, List<RegionVO> allList){List<RegionVO> voList = new ArrayList<>();for (RegionVO item : allList) {//如果相等if (Objects.equals(item.getParentId(), parentId)) {//递归,自己调自己item.setChildren(buildChildren(item.getId(), allList));voList.add(item);}}return voList;
}
这种就不必说了,一次查询所有数据出来,一共执行一次查询

4.总结
查询方式有很多,应该使用哪种需要猿们结合具体情况选择。
第一种情况:当整体数据量特别大 层级不深 需要按照某个根节点查询时,推荐使用第一、二种方式。
第二种情况:当需要查询整个树时,推荐使用第三种方式。
相关文章:
SpringBoot + MyBatis-Plus构建树形结构的几种方式
1. 树形结构 树形结构,是指:数据元素之间的关系像一颗树的数据结构。由树根延伸出多个树杈 它具有以下特点: 每个节点都只有有限个子节点或无子节点;没有父节点的节点称为根节点;每一个非根节点有且只有一个父节点&a…...
linux vscode 下开发
linux vscode 下开发 javajdk插件查看调用层次 java jdk 各种JAVA JDK的镜像分发 编程宝库 - 技术改变世界 jdk 镜像 ubuntu22.04 安装 # Linux x64 64位 jdk-8u351-linux-x64.tar.gztar -zxf jdk-8u351-linux-x64.tar.gz mv jdk1.8.0_351 jdk8/ vim ~/.pr…...
【工具】python代码编辑器--PyCharm下载安装和介绍
PyCharm是一种Python IDE(集成开发环境),由JetBrains打造。它带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试、语法高亮、项目管理、代码跳转、智能提示、自动完成、单元测试、版本控制等。此外,PyCharm还提供了一些高级功能,以用于支持Django框…...
SpringBoot第44讲:SpringBoot集成Redis - Redis分布式锁的实现之Jedis(setNXPX+Lua)
SpringBoot第44讲:SpringBoot集成Redis - Redis分布式锁的实现之Jedis(setNXPXLua) Redis实际使用场景最为常用的还有通过Redis实现分布式锁。本文是SpringBoot第44讲,主要介绍Redis实现分布式锁 文章目录 SpringBoot第44讲:SpringBoot集成Re…...
STM32F4X USART串口使用
STM32F4X USART串口使用 串口概念起始位波特率数据位停止位校验位串口间接线 STM32F4串口使用步骤GPIO引脚复用函数串口初始化函数串口例程 串口概念 串口是MCU与外部通信的重要通信接口,也是MCU在开发过程中的调试利器。串口通信有几个重要的参数,分别…...
python实现两个字符串比对差异点
一:代码实现 import difflib, re# 比较两个文本差异点 def compare_text_index(text1, text2):# 创建SequenceMatcher对象matcher = difflib.SequenceMatcher(a=text1, b=text2)# 获取差异报告diff_report = matcher.get_opcodes()# 检查差异报告中是否存在关键词错误for tag…...
SQLite数据库实现数据增删改查
当前文章介绍的设计的主要功能是利用 SQLite 数据库实现宠物投喂器上传数据的存储,并且支持数据的增删改查操作。其中,宠物投喂器上传的数据包括投喂间隔时间、水温、剩余重量等参数。 实现功能: 创建 SQLite 数据库表,用于存储宠…...
【Golang系统开发】搜索引擎(2) 压缩词典
写在前面 这篇文章我们就给出一系列的数据结构,使得词典能达到越来越高的压缩比。当然,和倒排索引记录表的大小相比,词典只占据了非常小的空间。那么为什么要对词典进行压缩呢? 这是因为决定信息检索系统的查询响应时间的一个重…...
clickhouse修改默认密码
1.明文密码 vim /etc/clickhouse-server/users.xml找到下面的语句,增加明文密码 <password>123456789</password> 2. sha256密码 # echo -n 123456789 | openssl dgst -sha256 (stdin) 15e2b0d3c33891ebb0f1ef609ec419420c20e320ce94c65fbc8c3312448eb225 修改…...
基于java在线捐赠系统设计与实现
摘要 近年来,随着网络的快速发展,由于网络的开放性和便利性,具有广阔的发展前景。 本文设计并实现了医药捐赠系统。通过分析确定由两个不同的用户组成,每个用户具有不同的功能。它还可以帮助用户在线求助、申请项目、发表留言等&a…...
【前端】vscode javascript 代码片段失效问题解决
1. 文件--首选项--用户代码片段-vue.json : 添加 // { // // Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and // // description. Add comma separated ids of the languages where the snippet is app…...
AE-卡通人物解说动画视频的制作
目录 1.导入卡通人物图片和音频文件 2.新建合成 3.在卡通人物图片上添加效果和表达式 4.在音频文件上添加效果和表达式 5.将卡通人物中的 CC Split2 中分割1 表达式链接到滑块中 6.卡通人物根据音频文件自动匹配口型。 AE制作卡通人物解说视频,卡通人物口型根据…...
Linux 查看日志
在 Linux 中,内核日志使用 printk 函数进行输出。你可以通过以下方法查看 printk 的日志: 使用 dmesg 命令: dmesg 这个命令会显示内核环缓冲区中的日志消息,包括使用 printk 输出的消息。你可以通过滚动浏览输出来查看完整的日志…...
使用IO多路复用select完成TCP循环服务器接收客户端消息并打印
服务器 客户端 结果...
unity之Input.GetKeyDown与Input.GetKey区别
文章目录 Input.GetKeyDown与Input.GetKey区别 Input.GetKeyDown与Input.GetKey区别 Input.GetKey 和 Input.GetKeyDown 是 Unity 中用于检测按键状态的两个不同函数。它们之间的区别在于何时触发。 Input.GetKey(KeyCode key): 这个函数会在用户按住指定的键时触发࿰…...
excel 核心快捷键用法
1、wps怎样只复制公示计算出来的数据 1.1、按下快捷键“CtrlC”,复制该单元格。 1.2、按下快捷键“ShiftCtrlV”,即“粘贴为数值”,即可只复制数字而不复制该单元格的公式 1.3、wps怎样只复制公示计算出来的数据_百度知道https://zhidao.baid…...
postgresql
源码安装 ./configure --prefix/apps/pgsql make world -j4 make install-world useradd -s /bin/bash -m -d /home/postgres postgres echo -e ‘123456\n123456’ | passwd postgres mkdir -pv /pgsql/data chown postgres:postgres /pgsql/data/ 设置环境变量 vim /etc/…...
AutoSAR配置与实践(基础篇)3.2 BSW中的I/O架构和模块详解
传送门 -> AUTOSAR配置与实践总目录 AutoSAR配置与实践(基础篇)3.2 BSW中的I/O架构和模块详解 一、 BSW中的I/O架构和模块详解1.1 I/O 模块构成1.2 各子模块功能详解二、举例说明I/O 模块如何配合完成信号采集2.1 硬件处理先行 (step1-4)2.2 AUTOSAR软件登场(step 5-7)2.3…...
基于Java+SpringBoot+Vue的学校田径运动会管理系统【源码+论文+演示视频+包运行成功】
博主介绍:✌擅长Java、微信小程序、Python、Android等,专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇🏻 不然下次找不到哟 Java项目精品实战案…...
使用 Visual Studio Code Docker 工具调试 .NET 容器
作者:Chet Husk 排版:Alan Wang Visual Studio Code Docker 工具已发布1.26.0版本,这个版本为使用 .NET SDK 构建和调试容器映像提供了内置支持。 VS Code 中的 Docker 调试 Visual Studio Code Docker 工具使开发人员可以轻松入门容器。它…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
