解决 MyBatis 一对多查询中,出现每组元素只有一个,总组数与元素数总数相等的问题

本文主要是介绍解决 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 一对多查询中,出现每组元素只有一个,总组数与元素数总数相等的问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/228742

相关文章

解决IDEA报错:编码GBK的不可映射字符问题

《解决IDEA报错:编码GBK的不可映射字符问题》:本文主要介绍解决IDEA报错:编码GBK的不可映射字符问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录IDEA报错:编码GBK的不可映射字符终端软件问题描述原因分析解决方案方法1:将命令改为方法2:右下jav

MyBatis模糊查询报错:ParserException: not supported.pos 问题解决

《MyBatis模糊查询报错:ParserException:notsupported.pos问题解决》本文主要介绍了MyBatis模糊查询报错:ParserException:notsuppo... 目录问题描述问题根源错误SQL解析逻辑深层原因分析三种解决方案方案一:使用CONCAT函数(推荐)方案二:

Redis 热 key 和大 key 问题小结

《Redis热key和大key问题小结》:本文主要介绍Redis热key和大key问题小结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、什么是 Redis 热 key?热 key(Hot Key)定义: 热 key 常见表现:热 key 的风险:二、

IntelliJ IDEA 中配置 Spring MVC 环境的详细步骤及问题解决

《IntelliJIDEA中配置SpringMVC环境的详细步骤及问题解决》:本文主要介绍IntelliJIDEA中配置SpringMVC环境的详细步骤及问题解决,本文分步骤结合实例给大... 目录步骤 1:创建 Maven Web 项目步骤 2:添加 Spring MVC 依赖1、保存后执行2、将新的依赖

Spring 中的循环引用问题解决方法

《Spring中的循环引用问题解决方法》:本文主要介绍Spring中的循环引用问题解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录什么是循环引用?循环依赖三级缓存解决循环依赖二级缓存三级缓存本章来聊聊Spring 中的循环引用问题该如何解决。这里聊

MySQL 中的 JSON 查询案例详解

《MySQL中的JSON查询案例详解》:本文主要介绍MySQL的JSON查询的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql 的 jsON 路径格式基本结构路径组件详解特殊语法元素实际示例简单路径复杂路径简写操作符注意MySQL 的 J

Spring Boot中JSON数值溢出问题从报错到优雅解决办法

《SpringBoot中JSON数值溢出问题从报错到优雅解决办法》:本文主要介绍SpringBoot中JSON数值溢出问题从报错到优雅的解决办法,通过修改字段类型为Long、添加全局异常处理和... 目录一、问题背景:为什么我的接口突然报错了?二、为什么会发生这个错误?1. Java 数据类型的“容量”限制

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

关于MongoDB图片URL存储异常问题以及解决

《关于MongoDB图片URL存储异常问题以及解决》:本文主要介绍关于MongoDB图片URL存储异常问题以及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录MongoDB图片URL存储异常问题项目场景问题描述原因分析解决方案预防措施js总结MongoDB图

SpringBoot项目中报错The field screenShot exceeds its maximum permitted size of 1048576 bytes.的问题及解决

《SpringBoot项目中报错ThefieldscreenShotexceedsitsmaximumpermittedsizeof1048576bytes.的问题及解决》这篇文章... 目录项目场景问题描述原因分析解决方案总结项目场景javascript提示:项目相关背景:项目场景:基于Spring