百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

MyBatisPlus 一对多分页异常处理及性能优化

bigegpt 2024-08-01 11:48 4 浏览

场景描述:

- 现有三张表:账户表(t_account),账户-角色关联表(account_role_link)和角色表(t_role)

- 对角色表进行分页查询并附带查询条件

- 查询结果需要关联角色对应的账户数据

## 1.异常情况

角色和用户存在一对多关系,可以使用collection对多个账户数据进行处理。对角色数据的封装如下(Java):

```java

public class RoleVO {

private String id;

private String name;

private String code;

private List<UserVO> users;

//此处省略getters&setters

}

```

```java

public class UserVO {

private String id;

private String username;

//此处省略getters&setters

}

```

RoleMapper.xml中的查询语句如下:

```xml

<resultMap id="UserVOMap" type="com.ramostear.console.domain.vo.UserVO">

<id property="id" column="uid"/>

<result property="username" column="username"/>

</resultMap>

<resultMap id="RoleVOMap" type="com.ramostear.console.domain.vo.RoleVO">

<id property="id" column="id"/>

<result property="name" column="name"/>

<result property="code" column="code"/>

<result property="status" column="status"/>

<result property="remark" column="remark"/>

<result property="createTime" column="create_time"/>

<result property="modifyTime" column="modify_time"/>

<collection property="users" resultMap="UserVOMap"/>

</resultMap>

<select id="queryPage" resultMap="RoleVOMap">

SELECT t_0.*,t_2.id AS uid,t_2.username

FROM t_role t_0

LEFT JOIN account_role_link t_1 ON t_0.id = t_1.role_id

LEFT join t_account t_2 ON t_2.id=t_1.account_id

<where>

<if test="keyword != null and keyword != ''">

AND t_0.name LIKE CONCAT('%',#{keyword}, '%')

</if>

<if test="status != null">

AND t_0.status=#{status}

</if>

</where>

ORDER BY t_0.create_time DESC,t_0.modify_time DESC

</select>

```

当前角色表中有13条数据,分页查询时,每页展示10条数据,执行查询,结果如下(JSON):

正常情况下,查询的结果中应该是10条角色数据,但此时只有8条(和预期结果有出入)。再观察控制台输入的SQL语句:

角色数据总数:13条,分页查询条数:10条,从查询结果数量上看,SQL没有问题。但黄色框中的数据会有一个小坑。

上述的SQL写法,是对角色和账户两张表JOIN后的结果集进行了分页(10条,其中有三条角色数据是重复的),而我们最开始的需求是对角色数据进行分页查询并带上角色对应的账户数据。Mapper中的collection在处理结果集时,会对黄色框中的数据进行合并收集(一对多处理),在进行实体对象映射时,MyBatisPlus将三条角色重复而用户不同的数据合并为一个RoleVO对象实例,这就导致了最终拿到的查询结果只有8条数据。

导致这个问题,是我们把原先对角色数据进行分页的需求,变成了对角色和账户JOIN后的数据进行分页,且在返回最终结果前,MyBatis 的collection又把数据进行了“合并”。

> 解决该问题的重点就是:对角色分页!对角色分页!对角色分页!

## 2.使用子查询

找到问题的所在,我们不应该对JOIN后的结果进行分页处理,而是先对角色数据进行分页处理,然后再处理角色和账户的一对多映射。

使用MyBatis提供的子查询,主表(t_role)查询不参杂对t_acctount表的处理,t_role表分页查询处理完成后,传递ro leId到子查询中,对关联的账户数据进行查找。

```xml

# ============================ 改造后的ResultMap =========================

<resultMap id="RoleVOMapBySubQuery" type="cn.zysmartcity.console.domain.vo.RoleVO">

<id property="id" column="id"/>

<result property="name" column="name"/>

<result property="code" column="code"/>

<result property="status" column="status"/>

<result property="remark" column="remark"/>

<result property="createTime" column="create_time"/>

<result property="modifyTime" column="modify_time"/>

<collection property="users" ofType="com.ramostear.console.domain.vo.UserVO" column="roleId" select="queryUserByRole">

<id column="uid" property="id" jdbcType="VARCHAR"/>

<result column="username" property="username" jdbcType="VARCHAR"/>

</collection>

</resultMap>

# ============================ 主表的查询语句 =========================

<select id="queryPageBySubQuery" resultMap="RoleVOMapBySubQuery">

SELECT role.*,role.id as roleId FROM t_role role

<where>

<if test="keyword != null and keyword != ''">

AND role.name LIKE CONCAT('%',#{keyword}, '%')

</if>

<if test="status != null">

AND role.status=#{status}

</if>

</where>

ORDER BY role.create_time,role.modify_time DESC

</select>

# ============================ 子表的查询语句 =========================

<select id="queryUserByRole" resultMap="UserVOMap">

SELECT t_1.id AS uid,t_1.username FROM account_role_link t_0

LEFT JOIN t_account t_1 ON t_1.id=t_0.account_id

WHERE t_0.role_id=#{roleId}

</select>

```

> 主表查询中role.id as roleId既为ResultMap中定义的需要传递到子查询queryUserByRole的roleId参数

执行SQL查询语句,观察返回的结果集:

数据总数:13条,当前查询条数:10条,分页异常的问题得以解决;但是,问题还有完全解决,我们先看控制台输出的SQL语句:

原本只需要发2次SQL语句(统计总数和分页)的查询,现在变成了发N+1条SQL语句(N为分页大小),再看执行的时间

13条数据分页查询,耗时66毫秒,如果数据更多呢?每页显示的数据量更大呢?子查询的这种方式显然是不太合理的。

## 3.自定义分页

> 解决思路:先对角色表进行分页查询,得到的结果集再和账户进行JOIN操作。

去掉ResultMap中colletion的column和select属性,保持和最开始的ResultMap一致:

```xml

<resultMap id="UserVOMap" type="com.ramostear.console.domain.vo.UserVO">

<id property="id" column="uid"/>

<result property="username" column="username"/>

</resultMap>

<resultMap id="RoleVOMap" type="com.ramostear.console.domain.vo.RoleVO">

<id property="id" column="id"/>

<result property="name" column="name"/>

<result property="code" column="code"/>

<result property="status" column="status"/>

<result property="remark" column="remark"/>

<result property="createTime" column="create_time"/>

<result property="modifyTime" column="modify_time"/>

<collection property="users" resultMap="UserVOMap"/>

</resultMap>

```

调整查询语句如下:

```xml

<select id="queryPageRecords" resultMap="RoleVOMap">

SELECT t_0.*,t_2.id AS uid,t_2.username

FROM (

# ===========处理角色的分页==============

SELECT t.id,

t.name,

t.code,

t.status,

t.remark,

t.create_time,

t.modify_time from t_role t

<where>

<if test="keyword != null and keyword != ''">

AND t.name LIKE CONCAT('%',#{keyword}, '%')

</if>

<if test="status != null">

AND t.status=#{status}

</if>

</where>

ORDER BY t.create_time,t.modify_time DESC

LIMIT #{offset}, #{size}

) t_0 # =======角色分页后的结果集,在同账户进行JOIN==========

left join account_role_link t_1 on t_0.id = t_1.role_id

left join t_account t_2 on t_2.id=t_1.account_id

</select>

```

> offset和size是上层(controller -> service)传递的分页参数,可以从Page<T>中获取到

改造后,我们还需要手动去统计一下角色表的数据总量(查询条件需要和分页查询中保持一致),最终改造如下:

RoleService.java

```java

public class RoleService {

//此处省略其他代码....

public Page<RoleVO> queryPage(String keyword,int status,Page<RoleVO> page) {

//执行分页查询

List<RoleVO> records = baseMapper.queryPageRecords(keyword, status, page.offset(), page.getSize());


//统计数据总数

LambdaQueryWrapper<Role> queryWrapper = new LambdaQueryWrapper<Role>()

.eq(status != null, Role::getStatus, status)

.like(StringUtils.isNotEmpty(keyword), Role::getName, keyword);

long total = this.count(queryWrapper);


//设置分页数据

page.setRecords(records);

page.setTotal(total);

return page;

}

}

```

执行SQL查询语句,观察返回的结果集:

数据总数:13条,当前查询条数:10条,返回数据正常,再观察控制台SQL日志和消耗的查询时间:

共发送了2次SQL语句,查询结果集为13条数据(3条角色重复,一对多合并后10条数据),耗时 8毫秒,相较之前的66毫秒,速度提升了不少,且减少了发SQL的次数。

相关推荐

得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践

一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...

warm-flow新春版:网关直连和流程图重构

本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...

扣子空间体验报告

在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...

spider-flow:开源的可视化方式定义爬虫方案

spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...

solon-flow 你好世界!

solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...

新一代开源爬虫平台:SpiderFlow

SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...

通过 SQL 训练机器学习模型的引擎

关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...

鼠须管输入法rime for Mac

鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...

Go语言 1.20 版本正式发布:新版详细介绍

Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...

iOS 10平台SpriteKit新特性之Tile Maps(上)

简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...

程序员简历例句—范例Java、Python、C++模板

个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...

Telerik UI for iOS Q3 2015正式发布

近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...

ios使用ijkplayer+nginx进行视频直播

上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...

IOS技术分享|iOS快速生成开发文档(一)

前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...

macOS下配置VS Code C++开发环境

本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...