首页 > 其他分享 > springboot实现多级嵌套并返回json格式的数据处理

springboot实现多级嵌套并返回json格式的数据处理

时间:2022-08-18 18:14:57浏览次数:72  
标签:菜单 springboot menu parentId 嵌套 json m2 id

菜单功能列表嵌套菜单解决方案

本文目录

        菜单功能列表嵌套菜单解决方案
            1、通过mapper定义sql的自循环,在查询时就完成菜单的嵌套
                数据库字段分析
                完善表字段
                分析sql逻辑
                定义实体类映射
                执行测试
            2、在后端逻辑中完成菜单关系的嵌套
            3、通过mybatis提供的循环递归去实现多级菜单
            4、总结

1、通过mapper定义sql的自循环,在查询时就完成菜单的嵌套
数据库字段分析

首先我们来看看数据库中的表字段


显然parentId就是用来做嵌套的切入点
完善表字段

在IDE中我们需要为Menu这个实体类定义个Lis列表数组,用来装载菜单嵌套的数据

注意:我们使用的持久层框架是MybatisPlus,所以添加注解告诉mybatisplus,这个List字段在数据库不存在

在这个是指在主表实体类中增加嵌套子表List

 

 


 

 



接下来我们在mapper.xml文件中定义sql语句,这里建议在Navicat或者sqlyog中编写sql语句,并模拟数据执行测试,这是为了假如后期报错能更容易的定位。如果sql之前能运行成功,那么出错一定在我们后端逻辑
分析sql逻辑

 

 


定义实体类映射

<!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.shuang.server.pojo.Menu">
        <id column="id" property="id" />
        <result column="url" property="url" />
        <result column="path" property="path" />
        <result column="component" property="component" />
        <result column="name" property="name" />
        <result column="iconCls" property="iconCls" />
        <result column="keepAlive" property="keepAlive" />
        <result column="requireAuth" property="requireAuth" />
        <result column="parentId" property="parentId" />
        <result column="enabled" property="enabled" />
    </resultMap>
<!-- 这里继承BaseResultMap,避免重复定义映射字段 -->
<resultMap id="Menus" type="com.shuang.server.pojo.Menu" extends="BaseResultMap">
        <collection property="children" ofType="com.shuang.server.pojo.Menu">
            <id column="id2" property="id" />
            <result column="url2" property="url" />
            <result column="path2" property="path" />
            <result column="component2" property="component" />
            <result column="name2" property="name" />
            <result column="iconCls2" property="iconCls" />
            <result column="keepAlive2" property="keepAlive" />
            <result column="requireAuth2" property="requireAuth" />
            <result column="parentId2" property="parentId" />
            <result column="enabled2" property="enabled" />
        </collection>
    </resultMap>

    <!--根据用户id查询用户菜单-->
    <select id="getMenusByAdminId" resultMap="Menus">
        SELECT DISTINCT m1.*,
                        m2.id          AS id2,
                        m2.url         AS url2,
                        m2.path        AS path2,
                        m2.component   AS component2,
                        m2.`name`      AS name2,
                        m2.iconCls     AS iconCls2,
                        m2.keepAlive   AS keepAlive2,
                        m2.requireAuth AS requireAuth2,
                        m2.parentId    AS parentId2,
                        m2.enabled     AS enabled2
        FROM yeb.t_menu m1,
             yeb.t_menu m2,
             yeb.t_admin_role ar,
             yeb.t_menu_role mr
        WHERE m1.id = m2.parentId
          AND m2.id = mr.mid
          AND mr.rid = ar.rid
          AND ar.adminId = #{id}
          and m2.enabled = true
    </select>



执行测试

成功完成父子菜单的数据嵌套

 

 


2、在后端逻辑中完成菜单关系的嵌套

在service层,根据用户id查询用户所拥有的的菜单

@Override
public List<SysMenuDto> getCurrentUserNav() {

    String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    SysUser sysUser = sysUserService.getByUsername(username);

    // 根据用户id查询用户所拥有的的菜单
    List<Long> navMenuIds = sysUserMapper.getNavMenuIds(sysUser.getId());
    List<SysMenu> sysMenus = this.listByIds(navMenuIds);

    // 转树状结构
    List<SysMenu> menuTree = buildTreeMenu(sysMenus);

    // 实体转DTO
    return convert(menuTree);
}

 

上面这段代码涉及了好几个方法,接下来我们逐个来解释

(String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 这里获取用户名是由于我们项目里集成了springsecurity后完成的自动登录,便可以直接获取用户登录后的信息

sysUserService.getByUsername(username)是我们在serviceImpl实现类中定义的方法,用的mybatisplus的内置方法

@Override
    public SysUser getByUsername(String username) {

        return getOne(new QueryWrapper<SysUser>().eq("username", username));
    }


sysUserMapper.getNavMenuIds(sysUser.getId());这是在mapper中定义的根据用户id查询菜单的信息的方法,这段代码使用到了多表连接,与上面类似这里不再详述

<select id="getNavMenuIds" resultType="java.lang.Long">
        SELECT Distinct rm.menu_id
        FROM vueadmin.sys_user_role ur
                 LEFT JOIN vueadmin.sys_role_menu rm ON ur.role_id = rm.role_id
        where ur.user_id = #{userId}
    </select>



this.listByIds(navMenuIds);this指的是本类,从下面这个图片可以看到通过mybatisplus的自动代码生成器,帮我们在service层完成对mapper的继承使用,但是如果涉及到复杂的sql语句的编写, 还是建议使用mapper

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9MS8Chj-1623554505661)(云e办项目总计.assets/image-20210613111018677.png)]

List<SysMenu> menuTree = buildTreeMenu(sysMenus);这里对从数据库查询出来的菜单列表,转换成树状的形式,也就是完成菜单关系的嵌套

private List<SysMenu> buildTreeMenu(List<SysMenu> menus) {

        List<SysMenu> finalMenus = new ArrayList<>();

        // 给当前的menu的所有子类都找到
        // 先各自寻找的各自的孩子
        for (SysMenu menu : menus){

            for (SysMenu child : menus){
                if (menu.getId().equals(child.getParentId())){
                    menu.getChildren().add(child);
                }
            }
            if (menu.getParentId()==0L){
                finalMenus.add(menu);
            }
        }

        // 提取出父节点
        return finalMenus;
    }



实体转树状方法的逻辑思路:我们先使用foreach(SysMenu menu : menus)循环遍历每一个菜单项,在第一次循环体中在进行一个foreach(SysMenu child : menus),循环对象跟前面的foreach是一样的,因为我们在SysMenu中有个参数parentId,所以在第二次循环的时候通过判断当第一次遍历的对象id跟第二次循环体的parentId是否相同,menu.getId().equals(child.getParentId()),如果相同就说明child是menu的子菜单项,就把child添加进menu,menu.getChildren().add(child);,如果不相同则不做任何处理,经过双重循环遍历就能够获取出菜单树\

onvert(menuTree);对返回结果的数据格式进行转换

我们在查询获取到菜单树状信息后,需要转换为前端想要接受的参数格式,所以我们在前面定义了SysMenuDto,正是要在这里进行转换,在返回转换后的Dto实体类,在转换过程中我们在判断菜单项是否有子菜单,如果有则进行递归转换的方法
3、通过mybatis提供的循环递归去实现多级菜单

mapper.xml文件

注意第17行的代码,column中的id是第一次查询出来children的id,通过children的id作为父id继续去查改children是否还存在children,一开始是用-1去查询parentId,将查询出来的记录的id作为第二次查询的parentId,进行递归查询知道parentId的child为空,递归结束

<?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.shuang.server.mapper.DepartmentMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.shuang.server.pojo.Department">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="parentId" property="parentId" />
        <result column="depPath" property="depPath" />
        <result column="enabled" property="enabled" />
        <result column="isParent" property="isParent" />
    </resultMap>

    <resultMap id="DepartmentWithChildren" type="com.shuang.server.pojo.Department" extends="BaseResultMap">
        <collection property="children" ofType="com.shuang.server.pojo.Department"
                    select="com.shuang.server.mapper.DepartmentMapper.getAllDepartments" column="id">
        </collection>
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, name, parentId, depPath, enabled, isParent
    </sql>

    <!--获取所有部门-->
    <select id="getAllDepartments" resultMap="DepartmentWithChildren">
        select
        <include refid="Base_Column_List"/>
        from yeb.t_department
        where parentId = #{parentId}
    </select>

</mapper>



 

 


 

 


4、总结

前一种方法是在定义sql时就已经完成对菜单关系的嵌套,并通过定义实体类映射关系来完成菜单查询

而第二种方法是直接从数据库查询出数据后,在对菜单id与父id的值进行比较,之后通过嵌套循环的方式来完成菜单关系的嵌套

最后一种方法则是通过mybatis的一种递归查询实现的

原文链接:https://blog.csdn.net/weixin_46195957/article/details/117871615

 

 

通过方法一实现返回嵌套JSON方法。

菜单功能列表嵌套菜单解决方案

本文目录

        菜单功能列表嵌套菜单解决方案
            1、通过mapper定义sql的自循环,在查询时就完成菜单的嵌套
                数据库字段分析
                完善表字段
                分析sql逻辑
                定义实体类映射
                执行测试
            2、在后端逻辑中完成菜单关系的嵌套
            3、通过mybatis提供的循环递归去实现多级菜单
            4、总结

1、通过mapper定义sql的自循环,在查询时就完成菜单的嵌套
数据库字段分析

首先我们来看看数据库中的表字段
在这里插入图片描述

显然parentId就是用来做嵌套的切入点
完善表字段

在IDE中我们需要为Menu这个实体类定义个Lis列表数组,用来装载菜单嵌套的数据

注意:我们使用的持久层框架是MybatisPlus,所以添加注解告诉mybatisplus,这个List字段在数据库不存在

在这里插入图片描述

接下来我们在mapper.xml文件中定义sql语句,这里建议在Navicat或者sqlyog中编写sql语句,并模拟数据执行测试,这是为了假如后期报错能更容易的定位。如果sql之前能运行成功,那么出错一定在我们后端逻辑
分析sql逻辑

在这里插入图片描述
定义实体类映射

<!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.shuang.server.pojo.Menu">
        <id column="id" property="id" />
        <result column="url" property="url" />
        <result column="path" property="path" />
        <result column="component" property="component" />
        <result column="name" property="name" />
        <result column="iconCls" property="iconCls" />
        <result column="keepAlive" property="keepAlive" />
        <result column="requireAuth" property="requireAuth" />
        <result column="parentId" property="parentId" />
        <result column="enabled" property="enabled" />
    </resultMap>
<!-- 这里继承BaseResultMap,避免重复定义映射字段 -->
<resultMap id="Menus" type="com.shuang.server.pojo.Menu" extends="BaseResultMap">
        <collection property="children" ofType="com.shuang.server.pojo.Menu">
            <id column="id2" property="id" />
            <result column="url2" property="url" />
            <result column="path2" property="path" />
            <result column="component2" property="component" />
            <result column="name2" property="name" />
            <result column="iconCls2" property="iconCls" />
            <result column="keepAlive2" property="keepAlive" />
            <result column="requireAuth2" property="requireAuth" />
            <result column="parentId2" property="parentId" />
            <result column="enabled2" property="enabled" />
        </collection>
    </resultMap>

    <!--根据用户id查询用户菜单-->
    <select id="getMenusByAdminId" resultMap="Menus">
        SELECT DISTINCT m1.*,
                        m2.id          AS id2,
                        m2.url         AS url2,
                        m2.path        AS path2,
                        m2.component   AS component2,
                        m2.`name`      AS name2,
                        m2.iconCls     AS iconCls2,
                        m2.keepAlive   AS keepAlive2,
                        m2.requireAuth AS requireAuth2,
                        m2.parentId    AS parentId2,
                        m2.enabled     AS enabled2
        FROM yeb.t_menu m1,
             yeb.t_menu m2,
             yeb.t_admin_role ar,
             yeb.t_menu_role mr
        WHERE m1.id = m2.parentId
          AND m2.id = mr.mid
          AND mr.rid = ar.rid
          AND ar.adminId = #{id}
          and m2.enabled = true
    </select>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

执行测试

成功完成父子菜单的数据嵌套

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k0wOIwZX-1623554505660)(云e办项目总计.assets/image-20210613105849868.png)]
2、在后端逻辑中完成菜单关系的嵌套

在service层,根据用户id查询用户所拥有的的菜单

@Override
public List<SysMenuDto> getCurrentUserNav() {

    String username = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    SysUser sysUser = sysUserService.getByUsername(username);

    // 根据用户id查询用户所拥有的的菜单
    List<Long> navMenuIds = sysUserMapper.getNavMenuIds(sysUser.getId());
    List<SysMenu> sysMenus = this.listByIds(navMenuIds);

    // 转树状结构
    List<SysMenu> menuTree = buildTreeMenu(sysMenus);

    // 实体转DTO
    return convert(menuTree);
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

上面这段代码涉及了好几个方法,接下来我们逐个来解释

(String) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 这里获取用户名是由于我们项目里集成了springsecurity后完成的自动登录,便可以直接获取用户登录后的信息

sysUserService.getByUsername(username)是我们在serviceImpl实现类中定义的方法,用的mybatisplus的内置方法

@Override
    public SysUser getByUsername(String username) {

        return getOne(new QueryWrapper<SysUser>().eq("username", username));
    }

    1
    2
    3
    4
    5

sysUserMapper.getNavMenuIds(sysUser.getId());这是在mapper中定义的根据用户id查询菜单的信息的方法,这段代码使用到了多表连接,与上面类似这里不再详述

<select id="getNavMenuIds" resultType="java.lang.Long">
        SELECT Distinct rm.menu_id
        FROM vueadmin.sys_user_role ur
                 LEFT JOIN vueadmin.sys_role_menu rm ON ur.role_id = rm.role_id
        where ur.user_id = #{userId}
    </select>

    1
    2
    3
    4
    5
    6

this.listByIds(navMenuIds);this指的是本类,从下面这个图片可以看到通过mybatisplus的自动代码生成器,帮我们在service层完成对mapper的继承使用,但是如果涉及到复杂的sql语句的编写, 还是建议使用mapper

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X9MS8Chj-1623554505661)(云e办项目总计.assets/image-20210613111018677.png)]

List<SysMenu> menuTree = buildTreeMenu(sysMenus);这里对从数据库查询出来的菜单列表,转换成树状的形式,也就是完成菜单关系的嵌套

private List<SysMenu> buildTreeMenu(List<SysMenu> menus) {

        List<SysMenu> finalMenus = new ArrayList<>();

        // 给当前的menu的所有子类都找到
        // 先各自寻找的各自的孩子
        for (SysMenu menu : menus){

            for (SysMenu child : menus){
                if (menu.getId().equals(child.getParentId())){
                    menu.getChildren().add(child);
                }
            }
            if (menu.getParentId()==0L){
                finalMenus.add(menu);
            }
        }

        // 提取出父节点
        return finalMenus;
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

实体转树状方法的逻辑思路:我们先使用foreach(SysMenu menu : menus)循环遍历每一个菜单项,在第一次循环体中在进行一个foreach(SysMenu child : menus),循环对象跟前面的foreach是一样的,因为我们在SysMenu中有个参数parentId,所以在第二次循环的时候通过判断当第一次遍历的对象id跟第二次循环体的parentId是否相同,menu.getId().equals(child.getParentId()),如果相同就说明child是menu的子菜单项,就把child添加进menu,menu.getChildren().add(child);,如果不相同则不做任何处理,经过双重循环遍历就能够获取出菜单树\

onvert(menuTree);对返回结果的数据格式进行转换

我们在查询获取到菜单树状信息后,需要转换为前端想要接受的参数格式,所以我们在前面定义了SysMenuDto,正是要在这里进行转换,在返回转换后的Dto实体类,在转换过程中我们在判断菜单项是否有子菜单,如果有则进行递归转换的方法
3、通过mybatis提供的循环递归去实现多级菜单

mapper.xml文件

注意第17行的代码,column中的id是第一次查询出来children的id,通过children的id作为父id继续去查改children是否还存在children,一开始是用-1去查询parentId,将查询出来的记录的id作为第二次查询的parentId,进行递归查询知道parentId的child为空,递归结束

<?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.shuang.server.mapper.DepartmentMapper">

    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.shuang.server.pojo.Department">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="parentId" property="parentId" />
        <result column="depPath" property="depPath" />
        <result column="enabled" property="enabled" />
        <result column="isParent" property="isParent" />
    </resultMap>

    <resultMap id="DepartmentWithChildren" type="com.shuang.server.pojo.Department" extends="BaseResultMap">
        <collection property="children" ofType="com.shuang.server.pojo.Department"
                    select="com.shuang.server.mapper.DepartmentMapper.getAllDepartments" column="id">
        </collection>
    </resultMap>

    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, name, parentId, depPath, enabled, isParent
    </sql>

    <!--获取所有部门-->
    <select id="getAllDepartments" resultMap="DepartmentWithChildren">
        select
        <include refid="Base_Column_List"/>
        from yeb.t_department
        where parentId = #{parentId}
    </select>

</mapper>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

在这里插入图片描述
在这里插入图片描述
4、总结

前一种方法是在定义sql时就已经完成对菜单关系的嵌套,并通过定义实体类映射关系来完成菜单查询

而第二种方法是直接从数据库查询出数据后,在对菜单id与父id的值进行比较,之后通过嵌套循环的方式来完成菜单关系的嵌套

最后一种方法则是通过mybatis的一种递归查询实现的
————————————————
版权声明:本文为CSDN博主「小爽帅到拖网速」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_46195957/article/details/117871615

标签:菜单,springboot,menu,parentId,嵌套,json,m2,id
From: https://www.cnblogs.com/haohaiyou/p/16599659.html

相关文章

  • 海康威视设备网络SDK封装+SpringBoot调用SDK
    最近在使用海康威视的摄像头进行车牌抓拍,使用了海康威视设备网络SDK,便稍做包装,便于项目调用。项目地址https://github.com/Mr-LuXiaoHua/hikivision-sdk海康设备网络SDK......
  • Vue 中引入 json 的三种方法
    Vue中引入json的三种方法 json的定义:JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。JSON是JS对象的字符串表示法,它使用文本表示一个JS对象......
  • Jmeter-变量的嵌套使用
    场景:有存在获取到多个登录账号,循环获取单个变量的情况。常用方法:${__BeanShell(vars.get("变量字段_${变量字段}"))}取值示例:   思维扩展:一:jmete......
  • PHP输出JSON安卓端无法解析 (2014-04-23 10:11:19)
    org.json.JSONException:Valueoftypejava.lang.StringcannotbeconvertedtoJSONObject解析服务器返回的Json串时,JSONObject对象抛出了这个异常。原以为是返回的j......
  • vscode command 'c_cpp.configuration edit json' not found 解决办法
    实际测试有效,解决方法如下:Ithinkit'sallabout IntelliSense for C_Cpp.AfterIre-enabledC_CppIntelliSense, gotodefinition worksagain...Lookslike......
  • SpringBoot中常用的参数注解
    1.@PathVariable获取浏览器请求路径的参数(rest风格)2.@RequestHeader获取请求头3.@RequestParam获取请求参数请求连接:接口:4.@CookieValue获取cookie的值......
  • uniapp 解决使用web-view嵌套H5页面以后返回失效的问题
    <template><view><web-view:src="src"></web-view></view></template><script>varwv;//计划创建的webviewexportdefault{data(){return{......
  • google gson解析json示例
    publicclassUser{//省略其它publicStringname;publicintage;publicStringemailAddress;......
  • 【Springboot】拦截器
    Springboot拦截器1.什么是拦截器?拦截器可以根据URL对请求进行拦截,主要应用于登陆校验、权限验证、乱码解决、性能监控和异常处理等功能。2.定义拦截器步骤在Spring......
  • vue学习之------vue-router【路由嵌套】
    1、声明子路由链接和子路由占位符<template><h2>>>about组件</h2><!--子路由链接--><router-linkto="/about/tab1">选项一</router-link> <router-link......