MybatisPlus一對多聯(lián)表查詢及分頁的解決過程
需求
查詢用戶信息列表,其中包含用戶對應(yīng)角色信息,頁面檢索條件有根據(jù)角色名稱查詢用戶列表;
需求分析
一個用戶對應(yīng)多個角色,用戶信息和角色信息分表根據(jù)用戶id關(guān)聯(lián)存儲,用戶和角色一對多進(jìn)行表連接查詢,
創(chuàng)建對應(yīng)表:
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 '角色I(xiàn)D',
`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 '角色I(xiàn)D',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶和角色關(guān)聯(lián)表';
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);
對應(yīng)實體類:
@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;
}
分頁問題說明
在使用一對多連接查詢并且分頁時,發(fā)現(xiàn)返回的分頁列表數(shù)據(jù)數(shù)量不對
比如這里查詢用戶對應(yīng)角色列表,如果使用直接映射,那么 roleList 的每個 Role 對象都會算一條數(shù)據(jù);比如查第一頁,一個用戶有三個角色每頁三條數(shù)據(jù),就會出現(xiàn)查出一個 User ,三個 Role 的這些情況,這它也算每頁三條(其實就只查到一個用戶)
分頁問題原因
mybatis-plus一對多分頁時,應(yīng)該使用子查詢的映射方式,使用直接映射就會出錯
所以直接映射適用于一對一,子查詢映射使用于一對多;
一對多場景一
查詢用戶表的內(nèi)容,角色表不參與條件查詢,用懶加載形式
// 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>
查詢結(jié)果

一對多場景二
查詢用戶表的內(nèi)容,角色表要作為查詢條件參與查詢,例如要根據(jù)角色名稱查詢出用戶列表
// 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>
查詢結(jié)果

性能優(yōu)化
原因:
場景一二中使用 select方式會觸發(fā)多次子查詢(SELECT *FROM sys_user_role ur inner join sys_role …),當(dāng)數(shù)據(jù)量大時會使查詢速度很慢。
場景二中查詢時產(chǎn)生的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各用戶就要執(zhí)行一百次子查詢,效率極低。
優(yōu)化解決方案
sql中只查詢sys_user相關(guān)信息并且做roleName 過濾,roleList在java代碼中用stream關(guān)聯(lián)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>
查詢結(jié)果
同場景二。
查詢時產(chǎn)生的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方式效率高一些
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot使用@Cacheable出現(xiàn)預(yù)覽工具亂碼的解決方法
直接使用注解進(jìn)行緩存數(shù)據(jù),我們再使用工具去預(yù)覽存儲的數(shù)據(jù)時發(fā)現(xiàn)是亂碼,這是由于默認(rèn)序列化的問題,所以接下來將給大家介紹一下SpringBoot使用@Cacheable出現(xiàn)預(yù)覽工具亂碼的解決方法,需要的朋友可以參考下2023-10-10
Java 的 Condition 接口與等待通知機(jī)制詳解
在 Java 并發(fā)編程里,實現(xiàn)線程間的協(xié)作與同步是極為關(guān)鍵的任務(wù),本文將深入探究Condition接口及其背后的等待通知機(jī)制,感興趣的朋友一起看看吧2025-05-05
Mybatis分頁插件Pagehelper的PageInfo字段屬性使用及解釋
這篇文章主要介紹了Mybatis分頁插件Pagehelper的PageInfo字段屬性使用及解釋,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05
一文詳解如何從零構(gòu)建Spring?Boot?Starter并實現(xiàn)整合
Spring Boot是一個開源的Java基礎(chǔ)框架,用于創(chuàng)建獨立、生產(chǎn)級的基于Spring框架的應(yīng)用程序,這篇文章主要介紹了如何從零構(gòu)建Spring?Boot?Starter并實現(xiàn)整合的相關(guān)資料,需要的朋友可以參考下2025-03-03
java 將字符串、list 寫入到文件,并讀取內(nèi)容的案例
這篇文章主要介紹了java 將字符串、list 寫入到文件,并讀取內(nèi)容的案例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09

