• Mybatis Plus一对多联表查询及分页解决方案


    需求

    查询用户信息列表,其中包含用户对应角色信息,页面检索条件有根据角色名称查询用户列表;

    需求分析

    一个用户对应多个角色,用户信息和角色信息分表根据用户id关联存储,用户和角色一对多进行表连接查询,

    创建对应表:

    CREATE TABLE `sys_user` (
      `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
      `name` varchar(50) DEFAULT NULL COMMENT '姓名',
      `age` int DEFAULT NULL COMMENT '年龄',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4  COMMENT='用户信息表';
    
    CREATE TABLE `sys_role` (
      `id` bigint NOT NULL AUTO_INCREMENT COMMENT '角色ID',
      `role_name` varchar(30) NOT NULL COMMENT '角色名称',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4  COMMENT='角色信息表';
    
    
    CREATE TABLE `sys_user_role` (
      `id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
      `user_id` bigint NOT NULL COMMENT '用户ID',
      `role_id` bigint NOT NULL COMMENT '角色ID',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4  COMMENT='用户和角色关联表';
    
    
    INSERT INTO tsq.sys_user (name,age) VALUES
    	 ('张三',18),
    	 ('王二',19);
    
    INSERT INTO tsq.sys_role (role_name) VALUES
    	 ('角色1'),
    	 ('角色2'),
    	 ('角色3'),
    	 ('角色4');
    
    INSERT INTO tsq.sys_user_role (user_id,role_id) VALUES
    	 (1,1),
    	 (1,2),
    	 (1,3),
    	 (2,4);
    
    
    
    • 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

    对应实体类:

    @Data
    @ApiModel("用户信息表")
    @TableName("sys_user")
    public class User implements Serializable {
    
        private static final long serialVersionUID = 1L;
        
        @ApiModelProperty("用户id")
        private Long id;
        @ApiModelProperty("姓名")
        private String name;
        @ApiModelProperty("年龄")
        private Integer age;
    }
    
    
    @Data
    @ApiModel("角色信息表")
    @TableName("sys_role")
    public class Role implements Serializable {
    
        private static final long serialVersionUID = 1L;
        
        @ApiModelProperty("角色id")
        private Long id;
        @ApiModelProperty("角色名称")
        private String roleName;
    }
    
    
    
    @Data
    @ApiModel("用户信息表")
    public class UserVo implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        @ApiModelProperty("用户id")
        private Long id;
        @ApiModelProperty("姓名")
        private String name;
        @ApiModelProperty("年龄")
        private Integer age;
        
        private List<Role> roleList;
    
    }
    
    
    
    • 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

    分页问题说明

    在使用一对多连接查询并且分页时,发现返回的分页列表数据数量不对
    比如这里查询用户对应角色列表,如果使用直接映射,那么 roleList 的每个 Role 对象都会算一条数据;比如查第一页,一个用户有三个角色每页三条数据,就会出现查出一个 User ,三个 Role 的这些情况,这它也算每页三条(其实就只查到一个用户)

    分页问题原因

    mybatis-plus一对多分页时,应该使用子查询的映射方式,使用直接映射就会出错
    所以直接映射适用于一对一,子查询映射使用于一对多;

    一对多场景一

    查询用户表的内容,角色表不参与条件查询,用懒加载形式

    // controller
      @GetMapping("/pageList")
    	public Map<String, Object> pageList(@RequestParam(required = false, defaultValue = "0") int offset,
    	                                    @RequestParam(required = false, defaultValue = "10") int pagesize) {
    		return userService.pageList(offset, pagesize);
    	}
      
      
    // serviceimpl
      @Override
    	public Map<String, Object> pageList(int offset, int pagesize) {
          List<UserVo> pageList = userMapper.pageList(offset, pagesize);
          int totalCount = userMapper.pageListCount();
          Map<String, Object> result = new HashMap<String, Object>();
          result.put("pageList", pageList);
          result.put("totalCount", totalCount);
    
          return result;
    	}
      
    // mapper.xml
    
       <resultMap id="getUserInfo" type="com.tsq.democase.onetomany.domain.vo.UserVo" >
            <result column="id" property="id" />
            <result column="name" property="name" />
            <result column="age" property="age" />
            <collection property="roleList" javaType="ArrayList" ofType="com.tsq.democase.onetomany.domain.Role"
                        select="getRolesByUserId" column="{userId = id}"/>
        </resultMap>
    
        <select id="getRolesByUserId" resultType="com.tsq.democase.onetomany.domain.Role">
            SELECT *
            FROM sys_user_role ur
                     inner join sys_role r on ur.role_id = r.id
            where ur.user_id = #{userId}
        </select>
        
        <select id="pageList"  resultMap="getUserInfo">
            SELECT *
            FROM sys_user
            LIMIT #{offset}, #{pageSize}
        </select>
        
        <select id="pageListCount" resultType="java.lang.Integer">
            SELECT count(1)
            FROM sys_user
        </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

    查询结果
    图片alt

    一对多场景二

    查询用户表的内容,角色表要作为查询条件参与查询,例如要根据角色名称查询出用户列表

    // controller
    	@GetMapping("/pageListByRoleName")
    	public Map<String, Object> pageListByRoleName(@RequestParam(required = false, defaultValue = "0") int offset,
    	                                    @RequestParam(required = false, defaultValue = "10") int pagesize,
    	                                    @RequestParam String roleName) {
    		return userService.pageListByRoleName(offset, pagesize, roleName);
    	}
      
      
    // serviceimpl
    	@Override
    	public Map<String, Object> pageListByRoleName(int offset, int pagesize,String roleName) {
          List<UserVo> pageList = userMapper.pageListByRoleName(offset, pagesize, roleName);
          int totalCount = userMapper.pageListCount();
          Map<String, Object> result = new HashMap<String, Object>();
          result.put("pageList", pageList);
          result.put("totalCount", totalCount);
    
          return result;
    	}
      
    // mapper.xml
       <resultMap id="getUserInfoByRoleName" type="com.tsq.democase.onetomany.domain.vo.UserVo" >
            <result column="id" property="id" />
            <result column="name" property="name" />
            <result column="age" property="age" />
            <collection property="roleList" javaType="ArrayList" ofType="com.tsq.democase.onetomany.domain.Role"
                        select="getRolesByUserIdAndRoleName" column="{userId = id,roleName = roleName}"/>
        </resultMap>
    
        <select id="getRolesByUserIdAndRoleName" resultType="com.tsq.democase.onetomany.domain.Role">
            SELECT *
            FROM sys_user_role ur
            inner join sys_role r on ur.role_id = r.id
            where ur.user_id = #{userId}
            <if test="roleName != null and roleName != ''" >
                and r.role_name LIKE concat('%', #{roleName}, '%')
            </if>
        </select>
    
        <select id="pageListByRoleName"  resultMap="getUserInfoByRoleName">
            SELECT temp.* FROM (
            SELECT distinct u.*,#{roleName} as roleName
            FROM sys_user u
            left join sys_user_role ur on u.id = ur.user_id
            left join sys_role r on r.id = ur.role_id
            <where>
                <if test="roleName != null and roleName != ''" >
                    r.role_name LIKE concat('%', #{roleName}, '%')
                </if>
            </where>
            ) temp
            LIMIT #{offset}, #{pageSize}
        </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
    • 53
    • 54
    • 55

    查询结果
    在这里插入图片描述

    性能优化

    原因:

    场景一二中使用 select方式会触发多次子查询(SELECT *FROM sys_user_role ur inner join sys_role …),当数据量大时会使查询速度很慢。

    场景二中查询时产生的sql日志如下:

    --  ==>  
    SELECT
        temp.* 
    FROM
        ( SELECT
            distinct u.*,
            '角色' as roleName 
        FROM
            sys_user u 
        left join
            sys_user_role ur 
                on u.id = ur.user_id 
        left join
            sys_role r 
                on r.id = ur.role_id 
        WHERE
            r.role_name LIKE concat('%', '角色', '%') ) temp LIMIT 0,
        10 
     --  ====>  
    SELECT
        * 
    FROM
        sys_user_role ur 
    inner join
        sys_role r 
            on ur.role_id = r.id 
    where
        ur.user_id = 1 
        and r.role_name LIKE concat('%', '角色', '%') 
     --  ====>  
    SELECT
        * 
    FROM
        sys_user_role ur 
    inner join
        sys_role r 
            on ur.role_id = r.id 
    where
        ur.user_id = 2 
        and r.role_name LIKE concat('%', '角色', '%') 
     --  ==>  
    SELECT
        count(1) 
    FROM
        sys_user 
    
    • 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

    sql可见如果有100各用户就要执行一百次子查询,效率极低。

    优化解决方案

    sql中只查询sys_user相关信息并且做roleName 过滤,roleList在java代码中用stream关联role并赋值roleList;

    // serviceimpl
      @Override
      public Map<String, Object> pageListByRoleName(int offset, int pagesize,String roleName) {
         // List pageList = userMapper.pageListByRoleName(offset, pagesize, roleName);
         List<UserVo> pageList = userMapper.pageListByRoleName2(offset, pagesize, roleName);
         List<Long> userIds = pageList.stream().map(UserVo::getId).collect(Collectors.toList());
         List<UserRoleVo> userRoleVos =  userMapper.getUserRoleByUserIds(userIds);
         Map<Long, List<UserRoleVo>> userRoleMap = userRoleVos.stream().collect(Collectors.groupingBy(UserRoleVo::getUserId, Collectors.toList()));
         pageList.forEach(u -> {
            List<UserRoleVo> roleVos = userRoleMap.get(u.getId());
            List<RoleVo> roles = BeanUtils.listCopy(roleVos, CopyOptions.create(), RoleVo.class);
            u.setRoleList(roles);
         });
         int totalCount = userMapper.pageListCount();
         Map<String, Object> result = new HashMap<String, Object>();
         result.put("pageList", pageList);
         result.put("totalCount", totalCount);
    
         return result;
      }
    
    // mapper.xml
    <select id="pageListByRoleName2"  resultType="com.tsq.democase.onetomany.domain.vo.UserVo">
        SELECT distinct u.*
        FROM sys_user u
        left join sys_user_role ur on u.id = ur.user_id
        left join sys_role r on r.id = ur.role_id
        <where>
            <if test="roleName != null and roleName != ''" >
                r.role_name LIKE concat('%', #{roleName}, '%')
            </if>
        </where>
        LIMIT #{offset}, #{pageSize}
    </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

    查询结果

    同场景二。

    查询时产生的sql如下:

      --  ==>  
      SELECT
          distinct u.* 
      FROM
          sys_user u 
      left join
          sys_user_role ur 
              on u.id = ur.user_id 
      left join
          sys_role r 
              on r.id = ur.role_id 
      WHERE
          r.role_name LIKE concat('%', '角色', '%') LIMIT 0, 10 
       --  ==>  
      SELECT
          ur.user_id ,
          r.id roleId,
          r.role_name 
      FROM
          sys_user_role ur 
      inner join
          sys_role r 
              on ur.role_id = r.id 
       --  ==>  
      SELECT
          count(1) 
      FROM
          sys_user 
     
    
    • 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

    由sql日志可见这种方式比纯sql方式效率高一些

  • 相关阅读:
    攻防世界---misc---心仪的公司
    神经网络控制系统的特点,神经网络控制属于
    HTML5,CSS,JS前端常见知识面试题
    装饰者模式
    Pandas入门
    Java网络通信:IP、端口、协议、IP地址操作类-InetAddress
    openGL通过点绘制图形
    天线设计中的负载牵引
    SpringBoot对于SpringMVC的支持
    最长公共子序列(LCS)与最长上升子序列(LIS)问题的相互转换
  • 原文地址:https://blog.csdn.net/weixin_38898423/article/details/127939212