Mybatis查询列表中的坑

前言

从一个Bug入手,看一个或许被很多人忽略的Mybatis使用中的大坑。
中间是排查思路。如果不想看排查过程,可以直接从[真正的解决方案]开始看。

Bug描述

JavaWeb项目中,使用Mybatis查询pg数据库。
在查询一个列表数据的时候,发现该列表的倒数第二页,缺了一条数据(pageSize=100,结果那一页只有99条)
把控制台打印的sql和参数日志拿出来,去数据库查,发现是可以查出100条的。甚至控制台上显示的sql日志也显示查出了100条。
看了一下sql,发现用了row_number()分页查询,类似于:

<select id="list_data", resultMap = "test_resultmap">
select * from (select row_number() row_num, t.*from (select a.*, b.* from A a left join B b on a.id=b.aid order by a.sort_field desc) as t
)  as t1
where row_num >= #{start}  and row_number <= #{end}
</select>

通过对比Mybatis查询的数据数据库执行相同sql得到的数据。发现表B中有部分数据出现了重复。
实际业务中,B表不应该有数据重复,而是应该和A表一一对应才对。
通过删除B表中的脏数据,再次验证,这个问题就算解决了。

站在业务角度上讲,这个问题就算解决了。
可站在技术角度讲,只是这个业务场景中B表不允许重复。可两表之间是一对多的关系,是很常见的设计。

排查

分页Total查询条件和List数据查询条件不一致

排查后发现Total的统计没问题。

row_number分页语句的问题

参考文章:使用 ROW_NUMBER () 排序后分页查询的坑
发现换成Limit查询,会出现同样的问题。
这个似乎很靠谱,或许逻辑是这样的:
因为B表重复,导致整个查询结果重复,排序字段又是A表的字段,所以那些重复的数据的排序字段值也是重复的——>排序不稳定 ——>同一个数据出现在第2也,也出现在第3页。当出现在第3页的时候,因为Mybatis的某种机制,这条奇怪的数据就被发现了,然后就被Mybatis过滤掉了。
试试网上给出的方案,给排序条件增加了一个排序字段。倒数第二页真的变成100条了。
问题解决了?
: 属于瞎猫碰见死耗子。其实根本就不是同一个问题。而且这个所谓的“解决方案”,还引起了排序混乱。
或者说是“正是因为排序被打乱了,所以之前那个错误的页跑到其他地方去了,只是倒数第二页变正常了而已”。然后就被误解为正确解释和方案了。

多字段排序

前面说:用了一个错误的方案居然“解决”了问题,实际上只是因为排序打乱了而已。为什么排序会被打乱? 是因为理解错了两个字段排序的写法:
order by a, b desc
等效于 order by a asc, b desc
而非等效于 order by a desc, b desc
也就是每个字段都要紧跟自己的排序方式,如果不写,则默认为asc

看源码

这部分放到另一篇博客里。

真正的解决方案

问题的真正根源其实在Mapper.xml的 ResultMapper中。也就是前面那个select语句中的resultMap = "test_resultmap",类似如下所示(业务代码中实际很复杂,这里就抽象出主要特征)

<resultMap type="com.test.Abc" id="test_resultmap"><id property="aId" column="id"/><result property="aName" column="aname"/><result property="aOrderField" column="aorderfield"/><result property="bid" column="bid"/><result property="bName" column="bname"/><result property="bOrderField" column="borderfield"/><collection property="otherInfo" javaType="ArrayList" ofType="com.test.C"><result property="cid" column="id"/><result property="cName" column="cname"/></collection></resultMap>`

这里的问题点就在于,多了一个嵌套类otherInfo,可select语句又没有真正的用上。

解决方案就是把这个多余的嵌套类配置删掉(实际我是另外重写了一个ResultMap,因为原来那个有其他select在引用),问题就解决了。

解释

Mybatis执行完sql拿到数据后,需要对数据进行封装。在上面这个例子中,就是把数据封装进
com.test.Abc对象中。
封装数据的时候,分两种情况:简单ResultMap嵌套ResultMap

  • 简单ResultMap:数据库返回几条就封装几个
  • 嵌套ResultMap:对返回的数据进行去重,然后封装返回

上面的示例Bug,一开始就是把ResultMap写成了嵌套类型,但实际上自己的返回值里根本没有嵌套类型的数据。
可Mybatis没那么智能,它发现ResultMap是嵌套类,就不管三七二十一的对数据进行了去重操作。可写这个方法人并没有这种去重意图。Bug因此就产生了。
大概就是写这个查询方法的人看到前面这个ResultMap里的字段包含了自己业务所需要的所有字段,所以就想着这个ResultMap是可以通用的,就直接引用在自己的select方法里了

另一个坑

现在假设查询sql返回的有嵌套类型,并且确实希望它嵌套在Abc里

<select id="list_data", resultMap = "test_resultmap">
select * from (select row_number() row_num, t.*from (select a.*, b.*, c.*from A a left join B b on a.id=b.aidleft join C c on a.id=c.aidorder by a.sort_field desc) as t
)  as t1
where row_num >= #{startIndex}  and row_number <= #{endIndex}
</select>

com.test.Abc如下

public class Abc {private int aId;private String aName;private int bId;private String bName;private C otherInfo;//嵌套类型...
}

此时就符合业务逻辑了吗?
:并没有。查询结果依然是根据A表去重,而我们实际希望的是根据A和B共同判断唯一条件。
问题出在<id property="aId" column="id"/>
当使用嵌套类型ResultMap时,Mybatis就会对结果集进行去重。
去重的标准:

  • id元素标签,就以此标签指定的字段进行去重
  • 没有id元素标签,就以整个ResultMap中所有字段拼起来作为去重标准(因此Mybatis官网和很多博客中,都会说这么一句话:id和result的唯一区别是id将结果标记为标识符属性,以便在比较对象实例时使用,这有助于提升性能

所查即所得?

mybatis在很多人的潜意识里,就是所查即所得(相比hibernate的高度封装,我们要的就是mybatis轻量级的自由)。
所以在这个问题的排查中,当我发现所有的线索都指向了:Mybatis对数据进行了去重操作
我就陷入了深深的自我怀疑(写了这么多年crud,竟然不知道Mybatis还有这种操作,Mybatis怎么会做这么“自作主张”的事呢)
实际上,Mybatis还真有一个合情合理的去重场景:

当我们要查询”一对多“的场景时,可以使用嵌套类型。可以直接获得一个类似于

// A和B是一对多关系
class A {
private int aId;
private String aName;
private List<B> bList;//嵌套B集合
}

这样的封装好的结果集。这样的结果是怎么来的呢。
简单想想就会明白:从数据库查出来的数据,肯定是类似于这样的结果:
aId1, aName1, bId1, bName1
aId1, aName1, bId2, bName2
aId2, aName2, bId3, bName3
aId2, aName2, bId4, bName4
要把这样的结果封装到A对象集合中,必然要对返回值中A表的数据进行去重。然后再把B表的字段值分别封装到A对象中

之所以一时联想不到“一对多”的场景,除了“去重”和“一对多”看起来确实相关性不强,更重要的我觉得还是这种场景应用的比较少的缘故。
工作过程中,虽然sql越写越复杂,可我们总是倾向于用复杂的sql,简单的结果集。 在潜意识里总觉得,用复杂的数据结构的 都是初学者的demo。因为结果类型越复杂,通用性越差,引起不必要问题的可能性就越大(事实上也确实如此)。可尽量不用,却不能不懂其原理。

引申

根据上面所说的,我们知道是Mybatis处理“一对多”时,自动对结果去重导致的问题。
那么再问一个问题:如果Mapper.xml里ResultMap用了嵌套类型,但结果类型确实这么写的
resultType = "java.util.Map"
Mybatis还会对结果进行去重吗?
答:不会。
因为Mybatis需要实体类和Mapper.xml里ResultMap同时是嵌套类型,才会进行去重。

Mybatis分页查询数据常见问题

问题描述原因解决方案
Mybatis查询列表数据量和sql执行的数据量不一致【本文的主题】执行了嵌套类的ResultMap【Mybatis结果集处理机制问题】把乱套用的嵌套类ResultMap处理一下
Mybatis查询列表数据的嵌套类型集合中只有一条数据(比如,订单表和商品表一对多,结果查出来商品集合里永远只有一条数据)主表的id名称和嵌套类的id名称重复了【Mybatis结果集处理机制问题】用别名区分一下
同一条数据出现在多个分页中列表查询排序字段重复了【sql问题,和Mybatis无关】增加一个排序字段
使用PageHelper引起的分页问题一般都是Mybatis框架直接简单粗暴套分页语句引起的。比如直接在你的sql后面增加limit语句(这一般很容易通过sql日志发现问题)【Mybatis处理sql语句问题】自己写分页,不要用PageHelper(有些博客说使用Mybatis的懒加载,也可以。但不建议,复杂的sql还是自己控制比较好)

参考

使用 ROW_NUMBER () 排序后分页查询的坑
resultmap中点id resultmap的id属性
Mybatis官网文档
MyBatis查询List返回数据只有少部分,因为结果去重了
Mybatis中用法(主要用于一对多去重)

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

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

相关文章

dataGridView 绑定List 显示内容不刷新

绑定后,原list值变动,显示内容会刷新 绑定后,list新添加的值时不会显示到界面,需要重新绑定list 微软的Bug 参考代码 public class Student{public string Name { get; set; }}List<Student> list new List<Student>();private void Form2_Load(object sender,…

练习 9 Web [SUCTF 2019]CheckIn (未拿到flag)

上传图片格式的木马文件&#xff1a; 返回 <? in contents!,存在PHP代码检测 上传非图片格式文件&#xff1a; 返回 不允许非image 修改木马PHP代码规避检测 <? ?> 改为 < script language“php”>< /script ><?php eval($_POST[shell]);?>…

JAVA实现压力测试

在Java中实现压力测试通常涉及到使用多线程来模拟并发操作。以下是一个简单的例子&#xff0c;使用Java的ExecutorService和Callable来执行并发的任务&#xff0c;进行简单的压力测试。 package useful; import java.time.LocalDateTime; import java.util.Calendar; import j…

PyTorch 深度学习(GPT 重译)(四)

第二部分&#xff1a;从现实世界的图像中学习&#xff1a;肺癌的早期检测 第 2 部分的结构与第 1 部分不同&#xff1b;它几乎是一本书中的一本书。我们将以几章的篇幅深入探讨一个单一用例&#xff0c;从第 1 部分学到的基本构建模块开始&#xff0c;构建一个比我们迄今为止看…

C#关键字 sealed、unsafe、virtual、volatile

sealed 应用于某个类时&#xff0c;sealed 修饰符可阻止其他类继承自该类。 在下面的示例中&#xff0c;类 B 继承自类 A&#xff0c;但没有类可以继承自类 B。 class A {} sealed class B : A {}还可以对替代基类中的 虚方法 或 属性的方法 或 属性 使用 sealed 修饰符。 这…

酷炫的粒子动态表白HTML源码

源码介绍 酷炫的粒子动态表白HTML源码&#xff0c;自己自定义文字&#xff0c;动态组合文字&#xff0c;进行表白&#xff0c;喜欢的朋友可以下载使用&#xff0c;很不错的表白HTML代码 下载地址 酷炫的粒子动态表白HTML源码

2024蓝桥杯每日一题(Flood Fill)

备战2024年蓝桥杯 -- 每日一题 Python大学A组 试题一&#xff1a;扫雷 试题二&#xff1a;动态网络 试题三&#xff1a;走迷宫 试题四&#xff1a;画图 试题一&#xff1a;扫雷 【题目描述】 扫雷是一种计算机游戏&#xff0c;在 2020 世纪 8080 年代…

【论文阅读】通过组件对齐评估和改进 text-to-SQL 的组合泛化

Measuring and Improving Compositional Generalization in Text-to-SQL via Component Alignment NAACL 2022| CCF B Abstract 在 text-to-SQL 任务中&#xff0c;正如在许多 NLP 中一样&#xff0c;组合泛化是一个重大挑战&#xff1a;神经网络在训练和测试分布不同的情况…

2024 Python3.10 系统入门+进阶(二):Python编程环境搭建

目录 一、Windows安装Python1.1 下载并安装 Python1.2 测试安装是否成功 二、Linux系统安装Python(新手可以跳过)2.1 基于RockyLinux系统安装Python(编译安装)2.2 基于Ubuntu系统安装Python(编译安装) 三、如何运行Python程序&#xff1f;3.1 Python 交互式编程3.2 编写Python源…

GB28181 —— 5、C++编写GB28181设备端,完成将USB摄像头视频实时转发至GB28181服务并可播放(附源码)

被测试的USB摄像头 效果 源码说明 主要功能模拟设备端&#xff0c;完成注册、注销、心跳等&#xff0c;同时当服务端下发指令播放视频时 设备端实时读取USB摄像头视频并通过OpenCV处理后实时转ps格式后封包rtp进行推送给服务端播放。 源码 /****remark: pes头的封装,里面的具…

开发环境配置本地hosts修改域名

一、找到hosts文件的位置&#xff1a; 方法一&#xff1a;windows系统下&#xff0c;直接 WinR 键输入&#xff1a; C:\WINDOWS\system32\drivers\etc 如图所示&#xff1a;输入完后点击确认 方法二&#xff1a;直接按照路径 C:\WINDOWS\system32\drivers\etc 去找 如图所示…

前端安全之XSS与CSRF

前端安全 XSS XSS(Cross-Site-Scripting)&#xff0c;跨站脚本攻击&#xff0c;因为缩写和 CSS 重叠&#xff0c;被别人抢先了&#xff0c;所以只能叫做 XSS。 攻击者可以利用这种漏洞在网站上注入恶意的客户端代码。若受害者运行这些恶意代码&#xff0c;攻击者就可以突破网…

Opencv | 图像基础知识

目录 一. 图像基础知识1. 颜色空间1.1 RGB颜色空间1.2 HSV颜色空间1.3 CMY(K)颜色空间 (了解) 2. 颜色图2.1 RGB三通道彩色图2.1.1 RGB图片数据格式 2.2 单通道灰度图 一. 图像基础知识 1. 颜色空间 1.1 RGB颜色空间 加法混色&#xff0c;彩色显示器 3通道&#xff1a;Red通道…

Selenium 学习(0.21)——软件测试之单元测试

我又回来了…… 其实我就是个渣&#xff0c;但是我努力、我尽心……所以也还能混得下去。这也是前面说的大厂和小厂之间的差异。 长得长的一定要割掉 其实对于软件测试这块&#xff0c;虽然没有系统学过&#xff0c;但是自己有全程编写过XXX系统&#xff0c;虽然不懂得一些专有…

什么样的网站不适合使用WordPress?

WordPress作为全球应用最广泛的CMS系统&#xff0c;很好很强大&#xff0c;被从多的网站使用。但是&#xff0c;也不是所有的网站。下面简站WP小编从自己多年WordPress建站经验的角度&#xff0c;给大家讲讲&#xff0c;有哪些网站不适合使用WordPress搭建。 1、功能特别多的功…

软考 系统架构设计师系列知识点之系统性能(2)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之系统性能&#xff08;1&#xff09; 所属章节&#xff1a; 第2章. 计算机系统基础知识 第9节. 系统性能 2.9.2 性能计算 性能指标计算的主要方法有定义法、公式法、程序检测法和仪器检测法。 常用的性能指标的经计算…

ASP.NET-Global.asax使用详解

本文介绍了如何使用Global.asax文件来增强ASP.NET Web应用程序的功能。首先&#xff0c;介绍了Global.asax文件的作用和基本功能。接着&#xff0c;详细探讨了在Global.asax中实现定时任务、应用程序级别的错误处理、应用程序启动和结束时执行特定逻辑等功能。随后&#xff0c;…

【Python 48小时速成 9】模块与包

在 Python 中&#xff0c;模块是一个包含了 Python 定义和声明的文件。包是一个包含了多个模块的文件夹&#xff0c;它还包含一个特殊的文件 __init__.py&#xff0c;该文件告诉 Python 解释器该文件夹是一个包。下面我们来分别讲解模块和包&#xff0c;并给出相应的示例代码&a…

QGIS编译(跨平台编译)056:PDAL编译(Windows、Linux、MacOS环境下编译)

点击查看专栏目录 文章目录 1、PDAL介绍2、PDAL下载3、Windows下编译4、linux下编译5、MacOS下编译1、PDAL介绍 PDAL(Point Data Abstraction Library)是一个开源的地理空间数据处理库,它专注于点云数据的获取、处理和分析。PDAL 提供了丰富的工具和库,用于处理激光扫描仪、…

ping 通ip,ping 不通域名

在linux 系统中&#xff0c;ping 通ip,ping 不通对应的域名时&#xff0c;可直接修改系统配置文件 vi /etc/hosts 加入 ip 域名