Java后台实现拖拽树状控件排序的数据持久化操作

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

背景

项目中有一个需求是创建一些树状多层级的目录这些目录为了方便还要支持随时可编辑顺序。技术选型上使用了ElementUI的可拖拽节点树即可实现拖拽节点编辑技术难度是在后端对拖拽后的顺序重排序持久化上。

设计思路

  1. 添加节点默认排序位置为最后
  2. 删除节点删除父节点时连带子节点一并删除并且要对已有树排序进行重排序
  3. 获取数据时需要将数据拼装成树
  4. 更新节点除了更新名字好像没啥要更新了
  5. 拖拽时对变动的位置进行上移或下移原理是变动排序数字可放到更新节点里一起做但我建议这个单独写一个方法单一职责

详细设计

约定

  1. 根节点的parentId为0
  2. 排序号由小到大
  3. 新建时排序号为父节点下最大的排序
  4. 前端会传输Node节点信息到后端

实体类Node

这是最基本的节点要素数据库中也要创建与之对应的字段。

@Data
public class Node {
    private Integer id; // 主键
    private Integer parentId; // 父节点
    private Integer sort;   // 排序
}

添加节点

添加节点需要获得新建节点在所在父节点下的最大排序值根节点是parentId为0的节点

	public void addNode(Node node) {
		node.setSort(getMaxSortFromParentId(node.getParentId()) + 1);
		nodeService.addNode(node);
	}

	public int getMaxSortFromParentId(Integer parentId) {
		// select count(1) from node where parent_id = #{parentId}
		....
	}

删除节点

删除节点需要考虑的是删除之后原先节点下面的节点排序号要 - 1。若删除父节点其子节点是删除或者放置外层。这里只做排序号更新操作。不考虑子节点的问题但这是不合规的实际开发中一定要处理这个问题。

public void deleteNode(Node node) {
	// 1. 直接删除节点
	nodeService.removeById(node.getId());
	// 2. 更新往下的排序号
	updateSort(node);
}

public void updateSort(Node node) {
	// update node set sort=sort-1 where parent_id = #{parentId} and sort >= #{sort}
	.....
}

更新节点

这个真没啥内容能在这个接口更新的都是一些业务数据比如名称

public void updateNode(Node node) {
	nodeService.update(node);
}

查询节点

查询节点我们要把查询结果组装成树的结构这就有很多种方法

  1. sql语句递归查询MySQL 8.0版本语法复杂维护困难
  2. 按数据库节点层级查库多次调库指数增长
  3. 一次查库程序封装不适用于数据量大的场景
  4. 数据库创建存储结构维护不便

我这里用的是第三种方案

定义节点树类

@Data
public class NodeTree {
	private Integer id;
	private Integer parent_id;
	private Integer sort;
	private List<NodeTree> childNodes;
}

实现业务方法

public List<NodeTree> getNodeTree() {
	// 查询数据库中全部的节点, 数据库做了排序等会就不用排序了
	// select * from node order by sort asc;
	List<Node> nodes = nodeService.qryNodeTree();
	List<NodeTree> results = new ArrayList<>();		// 返回结果集
	for (Node n : nodes) {
		// 没有父节点就是根节点从根节点出发去获取子节点
		if (StringUtils.hasText(node.getParentId())) {
			NodeTree root = new NodeTree();
			BeanUtils.copyProperties(n, root);
			findChild(root, nodes);
			results.add(root);
		}
	}
	return results;
}

public NodeTree findChild(NodeTree root, List<Node> nodes) {
	for (Node n : nodes) {
		// 如果当前遍历的节点的父id等于根节点id则执行操作
		if (n.getParentId() != null && n.getParentId().equals(String.valueOf(root.getId())) {
			// 如果未创建子节点集合则创建
			if (root.getChildNodes() == null) root.setChildNodes(new ArrayList<>());
			// 往下递归
			root.getChildNodes().add(findChild(n, nodes));
		}
	}
	return root;
}

拖拽时刷新排序号

前端的拖拽事件回调方法获得拖拽后的节点信息回传到后端
若子节点被拖拽到最外层成为根节点时前端约定要设置ParentId为0

@Transactional
public void updateSort(Node node) {
	// 获得旧的节点信息
	Node oldNode = nodeService.getById(node.getId());
	// 若同层级内拖拽则不需要变更根节点
	if (oldNode.getParentId().equals(node.getParentId())) {
		// 同级上移
		if (oldNode.getSort() > node.getSort()) {
			// 旧位置与新位置之间的元素要往下移一位
			// update node set sort=sort+1 where parent_id = #{parentId} and sort >= #{newSort} and sort < #{oldSort}
			nodeService.peerUpSort(node.getParentId(), node.getSort(), oldNode.getSort());
		}
		// 同级下移
		if (oldNode.getSort() < node.getSort()) {
			// 旧位置与新位置之间的元素上移一位
			// update node set sort=sort-1 where parent_id = #{parentId} and sort <= #{newSort} and sort > #{oldSort}
			nodeService.peerDownSort(node.getParentId(), node.getSort(), oldNode.getSort());
		}
	} else {
		// 非同级需要切换父节点原父节点移出位置往下元素上移一位新父节点移入位置往下元素下移一位

		// 新父节点元素下移
		// update node set sort=sort+1 where parent_id = #{parentId} and sort >= #{newSort}
		nodeService.upSort(node.getParentId(), node.getSort());
		// 原父节点元素上移
		// update node set sort=sort-1 where parent_id = #{parentId} and sort > #{oldSort}
		nodeService.downSort(oldNode.getParentId(), oldNode.getSort());
	}
	// 保存节点信息
	nodeService.updateById(node);
	
}

总结

很久没练习过数据结构了重新整理学习了一下这只是一个大概的方案可优化的地方很多比如对归档的节点已找完树结构的节点做一个移除操作能大幅度提升程序处理的速度可考虑使用链表来实现。具体的细节可根据业务场景补充。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java