在实际开发中,我们发现,其实很多数据结构是树状的,每个节点下面有不确定数量的节点,并且深度也不确定。这种数据结构应当如何进行处理呢?
其实我们的文件目录,就是这样的数据结构:
那么今天就以一个设计和查询一个简单的目录为例来进行讲解。
1,设计数据库表和类
我们首先还是设计数据库和类。
简单起见,我们这里的目录类只有id
和名字这两个基本信息。我们先设计数据库表如下:
可见除了基本信息以外,我还加了parent_id
字段表示某个目录的上一级目录,即为它的父目录。借助这个字段,我们是不是就可以查到一个目录之下所有的子目录了呢?
好的,我们继续设计类图,构建的目录类如下:
在Java的类里面,我们就没有设定parent_id
这个字段了,而是换成了子目录的集合。这样我们下面使用MyBatis进行级联查询到一个目录时,就直接把子目录放在这个集合即可。
这里给出目录表sql文件和类代码:
Java类:
packagecom.example.treehandle.dataobject; importlombok.Getter; importlombok.NoArgsConstructor; importlombok.Setter; importjava.io.Serializable; importjava.util.List; /*** 菜单类*/publicclassMenuimplementsSerializable { /*** 主键id*/privateintid; /*** 目录名*/privateStringname; /*** 该目录下的子目录*/privateList<Menu>childMenus; }
sql文件:
-- 初始化表droptable if exists `menu`;createtable `menu` ( `id` intunsigned auto_increment, `name` varchar(16)notnull, `parent_id` intnotnull,-- 这个目录的父目录id,如果这个目录是根目录那么这个字段值为0 primary key (`id`)) engine = InnoDB default charset = utf8mb4;-- 初始化测试数据insertinto `menu` (`name`, `parent_id`)values('音乐',0),('视频',0),('图片',0),('中文歌',1),-- “中文歌”目录是“音乐”的子目录('英文歌',1),-- “英文歌”目录是“音乐”的子目录('动物',3),-- “动物”目录是“图片”的子目录('二次元',3),-- “二次元”目录是“图片”的子目录('动漫',7),-- “动漫”目录是“二次元”的子目录('二次元游戏',7),-- “二次元游戏”目录是“二次元”的子目录('公主连结',9),-- “公主连结”目录是“二次元游戏”的子目录('碧蓝航线',9),-- “碧蓝航线”目录是“二次元游戏”的子目录('原神',9);-- “原神”目录是“二次元游戏”的子目录
那么重要的问题来了:怎么进行树状结构的级联查询呢?我们往下看。
2,MyBatis级联查询树状结构
首先还是定义DAO层,这里只写两个查询方法:
packagecom.example.treehandle.dao; importcom.example.treehandle.dataobject.Menu; importorg.apache.ibatis.annotations.Mapper; importjava.util.List; publicinterfaceMenuDAO { /*** 根据id查找目录*/MenugetById(intid); /*** 根据父目录id查找目录(查找某一目录下的所有子目录)*/List<Menu>getByParentId(intparentId); }
这里除了定义getById
方法,我们还定义了getByParentId
方法,这个就是用于帮助我们查到一个目录的子目录的方法。
然后就是编写MyBatis Mapper XML文件:
<mappernamespace="com.example.treehandle.dao.MenuDAO"><resultMapid="menuResultMap"type="com.example.treehandle.dataobject.Menu"><idcolumn="id"property="id"/><resultcolumn="name"property="name"/><!-- 查询以自身id为父节点的节点,实现往下递归查询 --><collectionproperty="childMenus"select="com.example.treehandle.dao.MenuDAO.getByParentId"column="id"fetchType="eager"/></resultMap><selectid="getById"resultMap="menuResultMap"> select * from `menu` where id = #{id} </select><selectid="getByParentId"resultMap="menuResultMap"> select * from `menu` where parent_id = #{parentId} </select></mapper>
这里是重点,在上面resultMap
节点中,我们定义了collection
节点表示我们的childMenus
属性是个集合,这个collection
节点中各个属性如下:
property
表示这个节点数据对应目录类中的哪个属性select
指定执行一个DAO查询方法并将其结果放在该节点column
用于执行select
属性中查询方法的参数fetchType
指定是懒获取还是全部获取
那么可见,我们在执行getById
查询方法时,遇到这个collection
节点,就会以查到的自己的id字段值作为参数去执行getByParentId
查询方法,即查询以自身id为父目录id的目录(说白了就是查询自己的所有子目录),这样就实现了往下递归查询,这样就能查询到一个目录以及其所有子目录的信息了。
这里,我们就完成了对树状结构的设计和查询了!
编写一个接口并查询一下试试,结果如下:
完整结果:
{ "id": 3, "name": "图片", "childMenus": [ { "id": 6, "name": "动物", "childMenus": [] }, { "id": 7, "name": "二次元", "childMenus": [ { "id": 8, "name": "动漫", "childMenus": [] }, { "id": 9, "name": "二次元游戏", "childMenus": [ { "id": 10, "name": "公主连结", "childMenus": [] }, { "id": 11, "name": "碧蓝航线", "childMenus": [] }, { "id": 12, "name": "原神", "childMenus": [] } ] } ] } ] }
可见,一个清晰明了的树状结构就查出来了。