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

news2025/7/8 16:01:17

文章目录

    • 需求
    • 需求分析
    • 分页问题说明
    • 分页问题原因
    • 一对多场景一
    • 一对多场景二
    • 性能优化
      • 优化解决方案

需求

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

需求分析

一个用户对应多个角色,用户信息和角色信息分表根据用户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);


对应实体类:

@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;

}


分页问题说明

在使用一对多连接查询并且分页时,发现返回的分页列表数据数量不对
比如这里查询用户对应角色列表,如果使用直接映射,那么 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>

查询结果
图片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>

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

性能优化

原因:

场景一二中使用 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 

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<UserVo> 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>  

查询结果

同场景二。

查询时产生的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 
 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/18532.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java计算机毕业设计ssm党支部在线学习

项目介绍 本党支部在线学习是针对目前学习的实际需求,从实际工作出发,对过去的学习系统存在的问题进行分析,完善用户的使用体会。采用计算机系统来管理信息,取代人工管理模式,查询便利,信息准确率高,节省了开支,提高了工作的效率。 本系统结合计算机系统的结构、概念、模型、原…

【深度学习】torch.utils.data.DataLoader相关用法 | dataloader数据加载器 | pytorch

文章目录前言一、DataLoader介绍二、DataLoader的子方法&#xff08;可调用方法&#xff09;前言 dataloader数据加载器属于是深度学习里面非常基础的一个概念了&#xff0c;基本所有的图像项目都会用上&#xff0c;这篇博客就把它的相关用法总结一下。 之所以要写这篇&#x…

1-1 开源许可证GPL, BSD, MIT, Mozilla, Apache, LGPL的介绍

文章目录前言开源许可证由来开源许可证类型开源许可证介绍GPL协议BSD协议MIT协议Mozilla许可Apache协议LGPL开源协议前言 开源许可证&#xff08;Open source licenses&#xff09;&#xff0c;广义来讲是指一种被用于计算机软件或其他产品的&#xff0c;允许在指定的条款内使…

zMemif: go语言高性能网络库

简介 开发zMemif的主要动机是go有很高的处理能力&#xff0c;但是内置的udp库的确有些寒酸&#xff0c; 纯c开发效率又有些低&#xff0c;虽然可以用nff-go来实现go和dpdk的融合&#xff0c;但是cgo编译的确有点烦人&#xff0c;而且这个项目似乎也死了。然后考虑到容器的场景…

单点登录以及实现(前后端分离和前后端不分离方式)

本文主要使用springSecurity来实现&#xff0c;其他实现请参照其原理自行研究。 一&#xff0c;单系统登录机制 1、http无状态协议 web应用采用browser/server架构&#xff0c;http作为通信协议。http是无状态协议&#xff0c;浏览器的每一次请求&#xff0c;服务器会独立处…

谷粒学院(三) 项目前端知识

一、VS Code 1、插件安装 为方便后续开发&#xff0c;建议安装如下插件&#xff08;红色矩形框标记的插件&#xff09;2、创建项目 vscode本身没有新建项目的选项&#xff0c;所以要先创建一个空的文件夹&#xff0c;如project_xxxx。 然后打开vscode&#xff0c;再在vscode里面…

二维随机向量的数学期望E与协方差σ

目录 1. 二维随机向量(X,Y)的数学期望EX, EY 2. 二维随机向量函数zg(X,Y)的数学期望EZ 3. 二维随机向量(X,Y)的方差DX, DY 4. 二维随机向量的性质&#xff08;和、积的数学期望E与方差D&#xff09; 5. 二维随机向量的协方差COV和相关系数ρ 5.1 协方差COV定义 5.2 协方…

私有数据传参

在串口工具进行输入&#xff1a; echo 1 > /dev/myled0 ---->led1灯点亮 echo 0 > /dev/myled0 ---->led1灯熄灭 echo 1 > /dev/myled1 ---->led1灯点亮 echo 0 > /dev/myled1 ---->led1灯熄灭 echo 1 > /dev/myled2 ---->led1灯点亮 echo 0 >…

java计算机毕业设计springboot+vue员工管理系统

项目介绍 本员工管理系统是针对目前村委会管理的实际需求,从实际工作出发,对过去的员工管理系统存在的问题进行分析,完善用户的使用体会。采用计算机系统来管理信息,取代人工管理模式,查询便利,信息准确率高,节省了开支,提高了工作的效率。 本系统结合计算机系统的结构、概念、…

无线蓝牙耳机什么牌子好一点?2022年蓝牙耳机推荐

喜欢听音乐&#xff0c;自然离不开耳机的支持&#xff0c;一款优质且时尚的耳机&#xff0c;能够带来极致的音效&#xff0c;可是该如何选择合适的耳机&#xff0c;成为了摆在眼前一个很重要问题如果这个问题一直萦绕在你的脑海中&#xff0c;那么下面的选择相信定不会错。 TO…

多线程异步方法Spring Security框架的SecurityContext无法获取认证信息的原因及解决方案

Spring Security是Spring生态提供的用户应用安全保护的一个安全框架&#xff0c;其提供了一种高度可定制的实现身份认证(Authentication)&#xff0c;授权&#xff08;Authorization&#xff09;以及对常见的web攻击手段做防护的方法。 之前我的博客Oauth2与Spring Security框架…

章鱼网络 Community Call #4|推进章鱼社区治理

全长5922字&#xff0c;预计阅读 15 分钟 撰文&#xff1a;MiX 微信交流&#xff1a;MixMetaverse 北京时间2022年11月8日21点&#xff0c;章鱼网络举行第4期 Community Call&#xff0c;10月8日是章鱼一周年庆典&#xff0c;所以本次 Community Call 我们讨论了最近2个月内很…

git可视化工具-sourceTree

1. 下载 官网的下载地址&#xff0c;可以根据自己的电脑操作系统选择合适的版本下载&#xff0c;我下载的是windows版本 Sourcetree | Free Git GUI for Mac and Windows A Git GUI that offers a visual representation of your repositories. Sourcetree is a free Git clie…

ArcGIS提取图斑四至点,可不再是四至范围哦

上一期我们向大家介绍了 ArcGIS计算图斑四至坐标原来这么简单&#xff01;可不要在走弯路哦_GIS思维的博客-CSDN博客​​ArcGIS计算图斑四至坐标原来这么简单&#xff01;可不要在走弯路哦https://blog.csdn.net/kinghxj/article/details/127941005 今天我们要向大家介绍一下 …

Gem5 for Ubuntu20.04

一、安装Ubuntu 参考教程&#xff1a;史上最全最新Ubuntu20.04安装教程&#xff08;图文&#xff09; - 知乎 (zhihu.com) 1. 查看Ubuntu版本号命令&#xff1a; lsb_release -a 显示如下&#xff1a; Distributor ID: Ubuntu //类别是ubuntu Description: Ubuntu 2…

LeetCode力扣刷题——指针三剑客之二:树

树 一、数据结构介绍 作为&#xff08;单&#xff09;链表的升级版&#xff0c;我们通常接触的树都是二叉树&#xff08;binary tree&#xff09;&#xff0c;即每个节点最多有 两个子节点&#xff1b;且除非题目说明&#xff0c;默认树中不存在循环结构。LeetCode 默认的树表示…

树的孩子兄弟链存储表示创建、遍历等算法

【实验目的】 1. 掌握树的孩子兄弟链存储表示。 2. 掌握树的创建、遍历等算法。 【问题描述】 树的创建及其操作。 【基本要求】 1. 创建树的孩子兄弟链式存储表示。假设以二元组(F,C)的形式输入一颗树的诸边&#xff0c;其中F表示双亲结点标识&#xff0c;C表示孩子结点…

python的opencv操作记录(九)——图像清晰度计算

文章目录图像清晰度计算的一般思路图像梯度图像梯度绝对值与梯度角度基于梯度的方式计算梯度算子1——Sobel算子计算梯度算子2——Laplacian算子梯度统计评分平均梯度梯度总和Demo图像清晰度计算的一般思路 定义图像清晰度是一个比较定制化&#xff0c;或者说比较偏业务属性的…

MySql面试

0. InnoDB与MyISAM的区别 1&#xff09;InnoDB支持事务&#xff0c;MyISAM不支持&#xff0c;对于InnoDB每一条SQL语言都默认封装成事务&#xff0c;自动提 交&#xff0c;这样会影响速度&#xff0c;所以最好把多条SQL语言放在begin和commit之间&#xff0c;组成一个事务&…

【Vue3+TS】Axios拦截器封装及跨域 [cors] 解决方案

【Vue3TS】Axios拦截器封装及跨域 [cors] 解决方案简述封装过程文件路径拦截器封装 —— Interceptor.tsAPI请求管理前端跨域的解决方案后端跨域的解决方案效果结语简述 我的项目采用 Vue3TypeScriptViteElement Plus 的组合&#xff0c;这个组合也是Vue版本退出3.x后官方推荐版…