美文网首页
java web 无限层级树状数据的增删改

java web 无限层级树状数据的增删改

作者: 东本三月 | 来源:发表于2020-01-31 14:50 被阅读0次

    1.说明

    • 相关使用框架
      Spring Boot
      Mybatis plus
      Layui 第三方组件treetable-lay 具体[https://www.jianshu.com/p/4c0117693ad7]
    • 无限层级数据的操作一般是通过后端代码或者sql的递归实现的.这里没有使用递归的方法,采用的是 直接保存层级信息到数据库的方式.该方法的增删改成本比较大,查询成本相对会比较小,对于改动频繁的数据不建议使用该方法.
    • 实现方式参考于https://gitee.com/stylefeng/guns
    • 部分非关键的代码不做说明,在之前文章有过解释.没有相关工具类可以先将代码注释

    2.model与数据库

    • 以基本角色表为例
    • 实体类使用了lombok,需要进行相关配置
    /**
     * 基本角色表
     */
    @Data
    @TableName(value = "base_role")
    public class BaseRole extends BaseModel {
    
        //角色别名
        @NotEmpty(message = "角色别名不能为空")
        private String code;
    
        //角色名称
        @NotEmpty(message = "角色名称不能为空")
        private String name;
    
        //父角色id
        private  Integer parent_id;
    
        //该角色所有的父角色id集合,数据格式:[0],[2],[152],
        private String parent_ids;
    
        //该角色的层级,一般与父角色的数量一致
        private Integer level;
    
        //角色说明
        private  String remark;
    
    }
    
    • 数据库表
    • 待补充

    3. 数据的新增与修改

    • 数据新增思路
      1.获取要保存的model
      2.设置 parent_ids属性值和 level属性值
      3.保存model
    • 数据修改思路
      1.获取修改后的model
      2.修改 parent_ids属性值和 level属性值
      3.获取角色修改前的所有子角色,对子角色的 parent_ids属性值和 level属性值进行修改并保存.
      4.对修改后的model进行完整更新,同步到数据库

    • 角色添加页面 (代码略)
    • 待补充
    • 控制层代码略过

    • 在数据的修改上,需要首先实现获取角色的所有子角色功能
    List<BaseRole> getLikeParentIds(Integer id);
    
       <select id="getLikeParentIds" resultType="com.****.model.base.BaseRole">
            SELECT a.* FROM base_role a where 1=1
            <if test="id != null and id != ''">
                and parent_ids LIKE CONCAT('%$[',#{id},'$]%') escape '$'
            </if>
        </select>
    
    • 在角色的parent_ids字段里,保存了该角色的所有上级角色的id,以 [0],[2],[152], 这样的格式记录.因此只需要加上一个模糊查询,就能方便的获取到所有子角色

    • 接下来需要实现 设置parent_ids属性值和 level属性值 的方法
      /**
         * 设置一个角色的parent_ids属性和level属性
         * @param model
         */
        public void setParentIds(BaseRole model){
            Assert.isTrue(model!=null,BaseExceptionEnum.PARA_ERROR);
            Integer parent_id=model.getParent_id();
    
            if(model.getParent_id()!=null){
                //如果修改了父角色,验证设置的父对象是否合法,不能将自身的子对象设置为自己的父对象
                if(!ModelUtil.isNew(model)){
                    BaseRole oldModel=super.getById(model.getId());
                    Assert.isTrue(oldModel!=null,BaseExceptionEnum.PARA_ERROR);
                    String old_parent_ids=oldModel.getParent_ids();
                    List<BaseRole> roles=roleDao.getLikeParentIds(oldModel.getId());
                    for(BaseRole role:roles){
                        Assert.isTrue(!role.getId().equals(model.getParent_id()),"不能将自身的子角色设置为自己的父角色");
                    }
                }
                //获取父角色的parent_ids
                //父角色的parent_ids + 父角色的id = 当前角色的parent_ids
                BaseRole parentModel=super.getById(parent_id);
                Assert.isTrue(parentModel!=null,BaseExceptionEnum.PARA_ERROR);
                model.setParent_ids(parentModel.getParent_ids()+"["+model.getParent_id()+"],");
    
                //设置层级
                //父角色的层级 + 1 = 当前角色的层级
                int level=0;
                if( parentModel.getLevel()!=null){
                    level = parentModel.getLevel();
                }
                model.setLevel(level+1);
            }else{
                model.setParent_ids("");
                model.setLevel(1);
            }
        }
    
    • 在角色存在父级信息的时候才进行相关处理,在处理前先验证一下是否设置成了闭合的父子关系.最后在对角色的parent_ids和level字段进行设置值.

    • 之后,实现编辑所需要的 更新所有子角色的 parent_ids属性值和 level属性值 的方法
    /**
         * 在修改后,更新所有子角色的结构
         * @param oldRole
         * @param newRole
         */
        public void updataAllChildParentIdsForEdit(BaseRole oldRole, BaseRole newRole){
            /*
             例子:
             oldRole是 A ,  A的父级是P  ,newRole是 N
             A 有 两个子级 ,是 A1 和 A2
             A2 有个子级是 A21
             * */
    
            /*
            获取所有的子角色
            相当于获取了 A1,A2,A21 三个对象
            * */
            List<BaseRole> roles=roleDao.getLikeParentIds(oldRole.getId());
            /*
             * 获取所有子级的公共父级元素信息
             * A1,A2,A21三个子级元素都有父级元素 [A],[P],
             * 将A的所有父级加上A本身,就是所有子级的公共父级元素
             * */
            String oldPidsPrefix = oldRole.getParent_ids() + "[" + oldRole.getId() + "],";
    
            //遍历所有子级对象
            for(BaseRole role:roles){
                //更新当前子角色的所有父级元素信息
                /*
                获取当前角色的非公共得到父级元素信息
                A21除了有公共的[A],[P],父级外,还有一个父级[A2]
                角色的所有父级信息是按层级顺序排的,截去前面的公共父级,后面的就是非公共父级元素信息
                * */
                String oldPidsSuffix = role.getParent_ids().substring(oldPidsPrefix.length());
                /*
                创建当前子角色新的父级元素信息
                N的所有父级元素 + N本身 +当前角色非公共父级元素信息 = 新的所有父级元素信息
                * */
                String role_parent_ids = newRole.getParent_ids() + "[" + newRole.getId() + "]," + oldPidsSuffix;
    
                role.setParent_ids(role_parent_ids);
                //更新层级数,角色的父级元素数量+1
                int level = StrUtil.count(role_parent_ids, "[");
                role.setLevel(level);
                super.updateById(role);
            }
        }
    
    • 代码很短,但逻辑上有有点绕.我在注释上用了一个例子进行解释说明.

    • 最后是实现新增与编辑的服务层方法
    /**
         * 添加或编辑
         * @param model
         */
        @Transactional(rollbackFor=Exception.class)
        @Override
        public void addOrEdit(BaseRole model) {
            Assert.isTrue(super.isUnique(model,"code"),"角色别名不能重复");
            Assert.isTrue(super.isUnique(model,"name"),"角色名称不能重复");
            
            //设置 parent_ids属性值和 level属性值
            setParentIds(model);
    
            //新增的数据直接保存
            if(ModelUtil.isNew(model)){
                super.saveOrUpdate(model);
            }else{
                //在进行修改时,更新子角色
                BaseRole oldModel=super.getById(model.getId());
                updataAllChildParentIdsForEdit(oldModel,model);
                //完整更新
                super.updateIntact(model);
            }
        }
    
    • 由于暂时没有其他的操作,我把新增和编辑合并到了一个方法里.思路还是一样的
    • 编辑时需要通过完整更新进行保存.完整更新是指 对于为null的字段也更新到数据库.
      因为会存在一些特殊情况,将一个有父级角色的数据修改成没有父级角色,那么修改后的parent_id值就会是null,在调用mybatis plus 提供的保存方法时,为null的值将视为不修改,数据库的值不会发生修改.
      完整更新的实现可以参考https://www.jianshu.com/p/8a83f29e79d4

    4.数据的删除

    • 数据删除的逻辑可根据实际的业务情况进行调整,这里实现了两种逻辑
      单一删除:删除一个角色数据,修改所有子角色的结构,用被删除角色的父角色代替被删除的角色.
      整体删除:删除一个角色数据,该角色下的所有子角色也将被删除.
        /**
         * 删除(不包括子角色)
         * @param ids
         */
        @Transactional(rollbackFor=Exception.class)
        @Override
        public void delete(Integer[] ids){
            Assert.isTrue(ids!=null, BaseExceptionEnum.PARA_ERROR);
            for(int i=0;i<ids.length;i++){
                Integer id=ids[i];
                BaseRole baseRole=super.getById(id);
                Assert.isTrue(baseRole!=null,BaseExceptionEnum.PARA_ERROR);
                updataAllChildParentIdsForDelete(baseRole);
               super.removeById(id);
            }
        }
    
        /**
         * 删除,包括子角色
         * @param ids
         */
        @Transactional(rollbackFor=Exception.class)
        @Override
        public void deleteChildren(Integer[] ids){
            Assert.isTrue(ids!=null, BaseExceptionEnum.PARA_ERROR);
            for(int i=0;i<ids.length;i++){
                Integer id=ids[i];
                BaseRole baseRole=super.getById(id);
                Assert.isTrue(baseRole!=null,BaseExceptionEnum.PARA_ERROR);
                deleteAllChild(baseRole);
                super.removeById(id);
            }
        }
      /**
         *在删除后,修改子角色的继承关系和层级
         * @param byDelete 被删除的角色
         */
        public void updataAllChildParentIdsForDelete(BaseRole byDelete){
            List<BaseRole> roles=roleDao.getLikeParentIds(byDelete.getId());
            for(BaseRole role:roles){
                String new_parent_ids=StrUtil.replace(role.getParent_ids(),"["+byDelete.getId()+"]","");
                role.setParent_ids(new_parent_ids);
    
                role.setParent_id(byDelete.getParent_id());
    
                role.setLevel(role.getLevel()-1);
                super.updateById(role);
            }
        }
    

    4.数据的查询

    • 没有采取在后端进行数据树状结构的封装.数据直接提供给前端,由 Layui 第三方组件treetable-lay来完成数据树状展示.

    相关文章

      网友评论

          本文标题:java web 无限层级树状数据的增删改

          本文链接:https://www.haomeiwen.com/subject/kcugthtx.html