在实际开发中,我们发现,其实很多数据结构是树状的,每个节点下面有不确定数量的节点,并且深度也不确定。这种数据结构应当如何进行处理呢?
其实我们的文件目录,就是这样的数据结构:
那么今天就以一个设计和查询一个简单的目录为例来进行讲解。
1,设计数据库表和类
我们首先还是设计数据库和类。
简单起见,我们这里的目录类只有id
和名字这两个基本信息。我们先设计数据库表如下:
可见除了基本信息以外,我还加了parent_id
字段表示某个目录的上一级目录,即为它的父目录。借助这个字段,我们是不是就可以查到一个目录之下所有的子目录了呢?
好的,我们继续设计类图,构建的目录类如下:
在Java的类里面,我们就没有设定parent_id
这个字段了,而是换成了子目录的集合。这样我们下面使用MyBatis进行级联查询到一个目录时,就直接把子目录放在这个集合即可。
这里给出目录表sql文件和类代码:
Java类:
package com.example.treehandle.dataobject;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
/**
* 菜单类
*/
@Getter
@Setter
@NoArgsConstructor
public class Menu implements Serializable {
/**
* 主键id
*/
private int id;
/**
* 目录名
*/
private String name;
/**
* 该目录下的子目录
*/
private List<Menu> childMenus;
}
sql文件:
-- 初始化表
drop table if exists `menu`;
create table `menu`
(
`id` int unsigned auto_increment,
`name` varchar(16) not null,
`parent_id` int not null, -- 这个目录的父目录id,如果这个目录是根目录那么这个字段值为0
primary key (`id`)
) engine = InnoDB
default charset = utf8mb4;
-- 初始化测试数据
insert into `menu` (`name`, `parent_id`)
values ('音乐', 0),
('视频', 0),
('图片', 0),
('中文歌', 1), -- “中文歌”目录是“音乐”的子目录
('英文歌', 1), -- “英文歌”目录是“音乐”的子目录
('动物', 3), -- “动物”目录是“图片”的子目录
('二次元', 3), -- “二次元”目录是“图片”的子目录
('动漫', 7), -- “动漫”目录是“二次元”的子目录
('二次元游戏', 7), -- “二次元游戏”目录是“二次元”的子目录
('公主连结', 9), -- “公主连结”目录是“二次元游戏”的子目录
('碧蓝航线', 9), -- “碧蓝航线”目录是“二次元游戏”的子目录
('原神', 9); -- “原神”目录是“二次元游戏”的子目录
那么重要的问题来了:怎么进行树状结构的级联查询呢?我们往下看。
2,MyBatis级联查询树状结构
首先还是定义DAO层,这里只写两个查询方法:
package com.example.treehandle.dao;
import com.example.treehandle.dataobject.Menu;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MenuDAO {
/**
* 根据id查找目录
*/
Menu getById(int id);
/**
* 根据父目录id查找目录(查找某一目录下的所有子目录)
*/
List<Menu> getByParentId(int parentId);
}
这里除了定义getById
方法,我们还定义了getByParentId
方法,这个就是用于帮助我们查到一个目录的子目录的方法。
然后就是编写MyBatis Mapper 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="com.example.treehandle.dao.MenuDAO">
<resultMap id="menuResultMap" type="com.example.treehandle.dataobject.Menu">
<id column="id" property="id"/>
<result column="name" property="name"/>
<!-- 查询以自身id为父节点的节点,实现往下递归查询 -->
<collection property="childMenus" select="com.example.treehandle.dao.MenuDAO.getByParentId" column="id" fetchType="eager"/>
</resultMap>
<select id="getById" resultMap="menuResultMap">
select *
from `menu`
where id = #{id}
</select>
<select id="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": []
}
]
}
]
}
]
}
可见,一个清晰明了的树状结构就查出来了。