解决 MyBatis 一对多查询中,出现每组元素只有一个,总组数与元素数总数相等的问题
文章目录
- 问题简述
- 场景描述
- 问题描述
- 问题原因
- 解决办法
问题简述
笔者在使用 MyBatis 进行一对多查询的时候遇到一个奇怪的问题。对于笔者的一对多的查询结果,出现了这样的一个现象:原来每个组里有多个元素,查询目标是查询所查的组,以及每个组中的元素。但查询的结果却是变成了这样:每组元素变得只有一个,且总组数与元素数总数相等。举个例子,假设一共有 3 个组,每组 4 个元素。而现在的查询结果却是,显示出了 12 个组,每组 1 个元素。
场景描述
笔者原来的表的情况比这要复杂很多,这里为了便于说明,简单抽象出这样一个情景。数据库中有很多用户(User),每个用户有他的好友分组(Folder),每个分组下面有该用户的好友(Contact)。现在需要查找这个用户所有的分组及好友,返回的数据结构需要是一个一个 List 分组,且一个分组中包含一个 List 好友。(List 指的是 Java 的一个内置的数据结构。)
-
User 表建表示例代码如下:
CREATE TABLE User (id VARCHAR(64) NOT NULL,name VARCHAR(64) NOT NULL,# ...为了简化说明,此表省略其它字段...PRIMARY KEY (id) ); -
Folder 表建表示例代码如下:
CREATE TABLE Folder (id VARCHAR(64) NOT NULL,userId VARCHAR(64) NOT NULL,name VARCHAR(64) NOT NULL,# ...为了简化说明,此表省略其它字段...PRIMARY KEY (userId, id),# 因为上面的是复合主键,所以自动创建的是联合索引,而其它表的外键引用需要的是单个索引INDEX idIndex (id),FOREIGN KEY (userId) REFERENCES User (id) ); -
Contact 表建表示例代码如下:
CREATE TABLE Contact (id VARCHAR(64) NOT NULL,# 表示此联系人属于谁的好友userId VARCHAR(64) NOT NULL,# 表示此联系人对应 User 中的 idlinkedUserId VARCHAR(64) NOT NULL,folderId VARCHAR(64) NOT NULL,# ...为了简化说明,此表省略其它字段...PRIMARY KEY (userId, id),# 因为上面的是复合主键,所以自动创建的是联合索引,而其它表的外键引用需要的是单个索引INDEX idIndex (id),# 同一个用户,不能拥有两个相同 ID 的 ContactUNIQUE (userId, linkedUserId),# 当复合主键成为外键时,必须整个复合主键一起作为外键,不能只引用复合主键其中的某个属性FOREIGN KEY (userId) REFERENCES Folder (userId),FOREIGN KEY (folderId) REFERENCES Folder (id),FOREIGN KEY (linkedUserId) REFERENCES User (id) ); -
建表示意图如下:


-
查询之后的 Java 数据结构如下:
@Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) public class FolderWithContacts {private Folder folder;private List<Contact> contacts; }其中,
@Setter @Getter @ToString @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) public class Folder {private String id;private String userId;private String name; }@Setter @Getter @ToString @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) public class Contact {private String id;private String userId;private String linkedUserId;private String folderId; } -
DAO 类代码如下:
public interface ContactDao {List<FolderWithContacts> getFolderWithContacts(String userId); }<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="XXX.xxx.ContactDao"><resultMap id="folderResultMap" type="XXX.xxx.Folder"><!-- property 指的是 Java 的字段名,column 指的是 SQL 的属性名 --><id property="id" column="folder_id"/><id property="userId" column="folder_user_id"/><result property="name" column="folder_name"/></resultMap><resultMap id="contactResultMap" type="XXX.xxx.Contact"><id property="id" column="contact_id"/><id property="userId" column="contact_user_id"/><result property="folderId" column="contact_folder_id"/><result property="name" column="contact_name"/></resultMap><resultMap id="folderWithContactsResultMap" type="XXX.xxx.FolderWithContacts"><!-- association 表示这是一个普通 Java 对象,而不是 Java 内置类型 --><association property="folder" resultMap="folderResultMap"/><!-- collection 表示这是一个 Java 集合。javaType 指的是 Java 集合的类型 --><collection property="contacts" javaType="java.util.ArrayList" resultMap="contactResultMap"/></resultMap><select id="getFolderWithContacts" resultMap="folderWithContactsResultMap">SELECT Folder.id AS folder_id,Folder.userId AS folder_user_id,Folder.name AS folder_name,Folder.sequence AS folder_sequence,Contact.id AS contact_id,Contact.userId AS contact_user_id,Contact.folderId AS contact_folder_id,Contact.name AS contact_name,Contact.description AS contact_descriptionFROM Contact,FolderWHERE Contact.folderId = Folder.idAND Contact.userId = #{userId}AND Folder.userId = #{userId}ORDER BY folder_sequence ASC</select></mapper>
问题描述
以上就是笔者用于某个用户的好友分组及每个分组下的好友的示例代码。但使用上面的代码的查询会出现问题。如果一个用户有 3 个好友,每组 4 个好友,则上述代码的查询结果会变成,该用户有 12 个好友分组,每个分组 1 个好友。而且,上面的整个查询过程在运行中都是正常的,不会发生报错。而且返回结果的每个字段都没有出现 null 值。
可以看出,上面的代码会导致无法区分好友与分组,把好友当成分组返回了。
问题原因
是什么原因出现上述问题呢?由于上面的整个查询过程都没有发生报错,且返回数据没有 null 值。因此不会是笔者的语法编写出现问题。
于是,笔者将上面的 SQL 单独在 MySQL 客户端命令行运行了一下,运行输出是正常的,确实是一个一对多查询的输出。一个一对多查询的输出,输出结果的数量应该和元素总个数相等,且同一个分组的所有元素关于这个分组的属性列的值应该也都是相等的。
这就说明并不是笔者 SQL 代码的问题,所以问题出现在 MyBatis 对 MySQL 输出结果的解析上。笔者非常确定,MyBatis 是肯定支持一对多查询的,因此一定是笔者关于 MyBatis 的 mapper 文件的编写出现问题。
笔者之后在不断地建新的更基本的表,进行一对多查询,终于让笔者发现了问题所在。
MyBatis 对于多表查询,要求组元素的字段必须是基本类型,而笔者编程时非常喜欢隔离、封装、解耦,擅自在上面将组元素的字段封装成了一个单独的类,然后把这个类的对象作为组元素的字段。在这种情况下,虽然 MyBatis 注入数据没有出问题,但它却没能识别出这是一对多查询的数据,因此将其当成一对一的数据来注入了。
可以看出,笔者在上面使用了 <association.../> 来映射一个 Java 对象,因此引发了上述问题。
解决办法
知道原因就好办了。可以直接将上面类 Folder 的字段合并在类 FolderWithContacts,然后去掉类 Folder。
改进后的相关代码如下:
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class FolderWithContacts {private String id;private String userId;private String name;private List<Contact> contacts;
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="XXX.xxx.ContactDao"><resultMap id="contactResultMap" type="XXX.xxx.Contact"><id property="id" column="contact_id"/><id property="userId" column="contact_user_id"/><result property="folderId" column="contact_folder_id"/><result property="name" column="contact_name"/></resultMap><resultMap id="folderWithContactsResultMap" type="XXX.xxx.FolderWithContacts"><id property="id" column="folder_id"/><id property="userId" column="folder_user_id"/><result property="name" column="folder_name"/><!-- collection 表示这是一个 Java 集合。javaType 指的是 Java 集合的类型 --><collection property="contacts" javaType="java.util.ArrayList" resultMap="contactResultMap"/></resultMap><select id="getFolderWithContacts" resultMap="folderWithContactsResultMap">SELECT Folder.id AS folder_id,Folder.userId AS folder_user_id,Folder.name AS folder_name,Folder.sequence AS folder_sequence,Contact.id AS contact_id,Contact.userId AS contact_user_id,Contact.folderId AS contact_folder_id,Contact.name AS contact_name,Contact.description AS contact_descriptionFROM Contact,FolderWHERE Contact.folderId = Folder.idAND Contact.userId = #{userId}AND Folder.userId = #{userId}ORDER BY folder_sequence ASC</select></mapper>
现在,这段代码运行起来,查询到的数据就是正常的了。
相关文章:
解决 MyBatis 一对多查询中,出现每组元素只有一个,总组数与元素数总数相等的问题
文章目录 问题简述场景描述问题描述问题原因解决办法 问题简述 笔者在使用 MyBatis 进行一对多查询的时候遇到一个奇怪的问题。对于笔者的一对多的查询结果,出现了这样的一个现象:原来每个组里有多个元素,查询目标是查询所查的组,…...
这应该是关于回归模型最全的总结了(附原理+代码)
本文将继续修炼回归模型算法,并总结了一些常用的除线性回归模型之外的模型,其中包括一些单模型及集成学习器。 保序回归、多项式回归、多输出回归、多输出K近邻回归、决策树回归、多输出决策树回归、AdaBoost回归、梯度提升决策树回归、人工神经网络、随…...
基于闪电连接过程优化的BP神经网络(分类应用) - 附代码
基于闪电连接过程优化的BP神经网络(分类应用) - 附代码 文章目录 基于闪电连接过程优化的BP神经网络(分类应用) - 附代码1.鸢尾花iris数据介绍2.数据集整理3.闪电连接过程优化BP神经网络3.1 BP神经网络参数设置3.2 闪电连接过程算…...
Linux性能优化--性能工具:网络
7.0 概述 本章介绍一些在Linux上可用的网络性能工具。我们主要关注分析单个设备/系统网络流量的工具,而非全网管理工具。虽然在完全隔离的情况下评估网络性能通常是无意义的(节点不会与自己通信),但是,调查单个系统在网络上的行为对确定本地配置和应用程…...
【Linux】线程互斥与同步
文章目录 一.Linux线程互斥1.进程线程间的互斥相关背景概念2互斥量mutex3.互斥量的接口4.互斥量实现原理探究 二.可重入VS线程安全1.概念2.常见的线程不安全的情况3.常见的线程安全的情况4.常见的不可重入的情况5.常见的可重入的情况6.可重入与线程安全联系7.可重入与线程安全区…...
敏捷开发中,Sprint回顾会的目的
Sprint回顾会的主要目的是促进Scrum团队的学习和持续改进。在每个Sprint结束后,团队聚集在一起进行回顾,以达到以下目标: 识别问题: 回顾会允许团队识别在Sprint(迭代)期间遇到的问题、挑战和障碍。这有助于…...
排序【七大排序】
文章目录 1. 排序的概念及引用1.1 排序的概念1.2 常见的排序算法 2. 常见排序算法的实现2.1 插入排序2.1.1基本思想:2.1.2 直接插入排序2.1.3 希尔排序( 缩小增量排序 ) 2.2 选择排序2.2.1基本思想:2.2.2 直接选择排序:2.2.3 堆排序 2.3 交换排序2.3.1冒…...
人大与加拿大女王大学金融硕士项目——立即行动,才是缓解焦虑的解药
!在这个经济飞速的发展的时代,我国焦虑症的患病率为7%,焦虑已经超越个体范畴,成为整个社会与时代的课题。焦虑,往往源于我们想要达到的,与自己拥有的所产生的差距。任何事情,开始做远比准备做更会给人带来成…...
老卫带你学---leetcode刷题(46. 全排列)
46. 全排列 问题: 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1:输入:nums [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#x…...
6.6 图的应用
思维导图: 6.6.1 最小生成树 ### 6.6 图的应用 #### 主旨:图的概念可应用于现实生活中的许多问题,如网络构建、路径查询、任务排序等。 --- #### 6.6.1 最小生成树 **概念**:要在n个城市中建立通信联络网,则最少需…...
100问GPT4与大语言模型的关系以及LLMs的重要性
你现在是一个AI专家,语言学家和教师,你目标是让我理解语言模型的概念,理解ChatGPT 跟语言模型之间的关系。你的工作是以一种易于理解的方式解释这些概念。这可能包括提供 例子,提出问题或将复杂的想法分解成更容易理解的小块。现在…...
Linux:mongodb数据逻辑备份与恢复(3.4.5版本)
我在数据库aaa的里创建了一个名为tarro的集合,其中有三条数据 备份语法 mongodump –h server_ip –d database_name –o dbdirectory 恢复语法 mongorestore -d database_name --dirdbdirectory 备份 现在我要将aaa.tarro进行备份 mongodump --host 192.168.254…...
凉鞋的 Godot 笔记 109. 专题一 小结
109. 专题一 小结 在这一篇,我们来对第一个专题做一个小的总结。 到目前为止,大家应该能够感受到此教程的基调。 内容的难度非常简单,接近于零基础的程度,不过通过这些零基础内容所介绍的通识内容其实是笔者好多年的时间一点点…...
数据结构 - 4(栈和队列6000字详解)
一:栈 1.1 栈的概念 栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原…...
MySQL InnoDB引擎深入学习的一天(InnoDB架构 + 事务底层原理 + MVCC)
目录 逻辑存储引擎 架构 概述 内存架构 Buffer Pool Change Buffe Adaptive Hash Index Log Buffer 磁盘结构 System Tablespace File-Per-Table Tablespaces General Tablespaces Undo Tablespaces Temporary Tablespaces Doublewrite Buffer Files Redo Log 后台线程 事务原…...
TX Text Control .NET Server for ASP.NET 32.0 Crack
TX Text Control .NET Server for ASP.NET 是VISUAL STUDIO 2022、ASP.NET CORE .NET 6 和 .NET 7 支持,将文档处理集成到 Web 应用程序中,为您的 ASP.NET Core、ASP.NET 和 Angular 应用程序添加强大的文档处理功能。 客户端用户界面 文档编辑器 将功能…...
Leetcode刷题详解——将x减到0的最小操作数
1. 题目链接:1658. 将 x 减到 0 的最小操作数 2. 题目描述: 给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作…...
精选免费热门api接口分享
IP归属地-IPv4城市级:根据IP地址查询归属地信息,支持到城市级,包含国家、省、市、和运营商等信息。IP归属地-IPv6城市级:根据IP地址(IPv6版本)查询归属地信息,支持到中国大陆地区(不…...
androidx.appcompat.widget.Toolbar最右边设置控件不能仅靠最右边
androidx.appcompat.widget.Toolbar最右边设置控件不能仅靠最右边 Android Toolbar左、中、右对齐-CSDN博客Android Toolbar左、中、右对齐默认的Android Toolbar中添加子元素view是从左到右依次添加。需要注意的是,Android Toolbar为自身的…...
Springboot整合WebSocket实现浏览器和服务器交互
Websocket定义 代码实现 引入maven依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>配置类 import org.springframework.context.annotation.Bean;i…...
告别音频调试噩梦:AP-0316 DSP语音处理模组全解析与实战选型
在嵌入式产品开发中,语音处理往往是考验硬件工程师耐心的“深水区”。无论是智能门禁的对讲系统,还是会议终端的免提通话,只要涉及到麦克风阵列、回声消除(AEC)和环境降噪(ENC),往往…...
3步完成AI化学逆合成规划:让复杂分子合成变得简单高效的终极指南
3步完成AI化学逆合成规划:让复杂分子合成变得简单高效的终极指南 【免费下载链接】aizynthfinder A tool for retrosynthetic planning 项目地址: https://gitcode.com/gh_mirrors/ai/aizynthfinder 你是否曾为设计复杂分子的合成路线而烦恼?传统…...
智赋能源 安筑未来|济南昊安光电亮相 2026 第六届中国贵州国际能源产业博览交易会
2026 年 5 月 18 日 —5月 20日,2026 第六届中国贵州国际能源产业博览交易会(简称 “贵州能源博览会”)在贵阳国际会议展览中心盛大启幕。本届展会聚焦能源产业数字化转型、绿色低碳发展与安全高效生产,汇聚能源领域全产业链优质企…...
Ryujinx终极指南:免费开源Switch模拟器快速入门与性能优化
Ryujinx终极指南:免费开源Switch模拟器快速入门与性能优化 【免费下载链接】Ryujinx 用 C# 编写的实验性 Nintendo Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/ry/Ryujinx Ryujinx是一款基于C#开发的开源Nintendo Switch模拟器࿰…...
ElevenLabs湖北话语音合成:从零部署到商用级TTS的7大避坑步骤(附武汉/宜昌/襄阳三方言测试数据)
更多请点击: https://kaifayun.com 第一章:ElevenLabs湖北话语音合成的技术定位与方言价值 ElevenLabs 作为全球领先的AI语音生成平台,其核心能力聚焦于高保真、情感化、多语言的文本到语音(TTS)合成。尽管官方尚未正…...
从一次‘迷路’说起:手把手调试LTE终端TAU失败问题(附Wireshark抓包分析)
从一次‘迷路’说起:手把手调试LTE终端TAU失败问题(附Wireshark抓包分析) 清晨的地铁站里,一位工程师盯着手机屏幕上反复跳出的"无服务"提示皱起眉头——这已经是本周第三次收到用户投诉在A区到B区的通勤路上出现信号中…...
Redis 主从复制与哨兵机制详解:从原理到高可用实战
Redis 主从复制与哨兵机制详解:从原理到高可用实战 1. 引言 Redis 作为高性能的键值存储系统,在生产环境中常面临两大挑战:单点故障(一个节点宕机导致服务不可用)和读写压力(单节点无法承载高并发读请求&am…...
TurboVNC终极指南:如何快速搭建高性能远程桌面系统
TurboVNC终极指南:如何快速搭建高性能远程桌面系统 【免费下载链接】turbovnc Main TurboVNC repository 项目地址: https://gitcode.com/gh_mirrors/tu/turbovnc TurboVNC是一个专为高性能图形应用优化的远程桌面解决方案,特别适合3D渲染、视频处…...
Cacti插件开发实战:从零开始创建自定义插件
Cacti插件开发实战:从零开始创建自定义插件 【免费下载链接】cacti Cacti ™ 项目地址: https://gitcode.com/gh_mirrors/ca/cacti Cacti是一款强大的网络监控和数据采集工具,通过插件系统可以轻松扩展其功能。本文将带你从零开始,掌握…...
如何快速配置Live Server Web Extension:提升开发效率的完整指南
如何快速配置Live Server Web Extension:提升开发效率的完整指南 【免费下载链接】live-server-web-extension It makes your existing server live. This is a browser extension that helps you to live reload feature for dynamic content (PHP, Node.js, ASP.N…...
