通常前端页面会设计一个树型类型的菜单,而这些菜单的内容又需要根据我们的业务而定,这些数据是动态变化的
那么我们的后端该如何设计数据库呢?
这里我们采用id-parentId的形式进行设计数据库
只要知道了一个跟结点的id,我们就可以根据这个id看成parentId去数据库中查询
where parentId=#{parentId}
就可以查到一个结点的子节点,这样一层一层遍历。就可以遍历出整个树形结构。当然上面只是简单的说一下,具体的逻辑还要看下面的讲解。
如何设计数据库
首先每个结点需要需要一个唯一的i主键id。另外需要一个parentId记录该结点的父节点。这样我们可以在最开始获取根节点的所有id,在把根结点的id当作parentId递归进行查询,这样我们就可以获取所有的结点数据了。
下面是一个很简单的数据库设计,但是可以用于讲解。
我们主要关注的是id和parentId。titile相当于结点的名字。其他字段可以不用管了。
这里我们将这个树型结构列表看成课程列表,如下:
每一个title代表的是一个课程信息。
java类如何设计
首先我们需要在java中创建一个属性和该表字段一模一样的类(原因就不说了,做过java开发的应该都懂):
说明:下面使用了Lombok、Swagger、mybatis-plus 不懂可以省略跳过,对后面得讲解无影响
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("edu_subject")
@ApiModel(value="Subject对象", description="课程科目")
public class Subject implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "课程类别ID")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private String id;
@ApiModelProperty(value = "类别名称")
private String title;
@ApiModelProperty(value = "父ID")
private String parentId;
@ApiModelProperty(value = "排序字段")
private Integer sort;
@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
}
另外,与前端的数据进行交互,可能不会交互数据库中的全部字段,所以我们需要根据前端需要的数据再设计一个类,里面的属性为前端所需要的部分数据。我的设计如下:
/**
* 前后端数据交互所使用的类
* @author:zhengjian
*/
@Data
@ApiModel("课程树形列表")
public class SubjectQueryVo {
@ApiModelProperty("课程表id")
private String id;
@ApiModelProperty("课程标题")
private String title;
@ApiModelProperty("课程排名")
private Integer sort;
@ApiModelProperty("课程子节点")
private List<SubjectQueryVo> children;
}
该类放在vo包下,都是用来进行前后端数据传输的类。
可以看出这里多出了一个children属性,主要是存放自己的孩子结点的。可以把这里理解成数据结构中树的链式存储。
sql语句如何设计
在开发中通常会使用mybatis-plus,简单的sql逻辑可以直接调用mybatis-plus的api,但是对于复杂的逻辑还是需要我们在xml中编写sql语句
下面是根据parentId查询结点的代码
<mapper namespace="com.zj.service.edu.mapper.SubjectMapper">
<select id="selectNestedListByParentId" parameterType="String" resultMap="nestedSubject">
select id,sort,title from edu_subject where parent_id = #{parentId}
</select>
<resultMap id="nestedSubject" type="com.zj.service.edu.vo.SubjectQueryVo">
<id column="id" property="id"></id>
<result column="sort" property="sort"></result>
<result column="title" property="title"></result>
<collection property="children" column="id" select="selectNestedListByParentId"
ofType="com.zj.service.edu.vo.SubjectQueryVo">
</collection>
</resultMap>
</mapper>
我直接贴出了代码:
这里使用到了mybatis的级联查询。
因为在SubjectQueryVo 中有List类型的children,我们就需要使用resultmap进行数据与字段的映射绑定了。在resultMap中我们设计好查询到的数据与SubjectQueryVo 的映射关系。另外resultMap的Collection就是用来处理List着中国情况的。
property
用来指定SubjectQueryVo 中的属性
column
表示执行此次Collection查询需要提供的查询变量
select
表示执行查询Collection中每条数据需要执行的sql语句,我们上面的id就可以用作变量传入
ofType
表示Collection中每个数据的数据类型是什么
我们这里相当于是递归查询了,所以select就是我们写的select语句
我们把根节点的parentId设置为0;
其他孩子结点的parentId设置为对应父节点id。
这样只要我们开始传入的id为0,就可以遍历整个树
下面看看service和Controller层的设计
service
//传入的parentId为0,这样就可以遍历整个结点树
@Override
public List<SubjectQueryVo> nestedList() {
return subjectMapper.selectNestedListByParentId("0");
}
controller
@ApiOperation("嵌套数据列表")
@GetMapping("/nested-list")
public Result nestedList(){
List<SubjectQueryVo> subjectQueryVos = subjectService.nestedList();
return Result.ok().data("items",subjectQueryVos);
}
把vo对象传给前台,我们看看数据是怎么样的:
"items": [
{
"id": "1507540020762435586",
"title": "后端开发",
"sort": 0,
"children": [
{
"id": "1507540020909236225",
"title": "Java",
"sort": 0,
"children": []
},
{
"id": "1507540021043453954",
"title": "Python",
"sort": 0,
"children": []
}
]
},
{
"id": "1507540021110562818",
"title": "前端开发",
"sort": 0,
"children": [
{
"id": "1507540021177671681",
"title": "HTML/CSS",
"sort": 0,
"children": []
},
{
"id": "1507540021244780546",
"title": "JavaScript",
"sort": 0,
"children": []
}
]
},
{
"id": "1507540021311889409",
"title": "数据库开发",
"sort": 0,
"children": [
{
"id": "1507540021374803970",
"title": "mysql",
"sort": 0,
"children": []
},
{
"id": "1507540021441912833",
"title": "oracle",
"sort": 0,
"children": []
}
]
}
]
可以看出我们已经成功的遍历了整棵树了。