一对多 & 多对一 关联查询

数据库准备:

一个班级表,字段:班级ID + 班级名称

一个学生表,字段:学生ID + 学生姓名 + 所属的班级ID

# 班级表 班级ID+班级名称
CREATE TABLE t_clazz(
`id` INT(2) PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(50)
); INSERT INTO t_clazz(`name`)
VALUES
('Java01'),
('Java02'),
('Java03'),
('Java04'),
('Java05'); # 学生表 对应一个班级
CREATE TABLE t_student(
`id` INT(2) PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(50),
`clazz_id` INT(2),
FOREIGN KEY(`clazz_id`) REFERENCES t_clazz(`id`)
); # 插入学生信息
INSERT INTO t_student(`name`,`clazz_id`)
VALUES
('学员1',1),
('学员2',1),
('学员3',1),
('学员4',2),
('学员5',2),
('学员6',2),
('学员7',3),
('学员8',3),
('学员9',3),
('学员10',4),
('学员11',4),
('学员12',4),
('学员13',5),
('学员14',5),
('学员15',5);

为了保证演示整洁,重新建立一个模块演示

建立ORM实体类

班级类

一个班级中存在若干个学生

所以如果查询一个班级的全学生信息,应该使用集合容器存储学生的信息

package main.java.cn.dai.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.ibatis.type.Alias; import java.util.List; /**
* @author ArkD42
* @file Mybatis
* @create 2020 - 05 - 30 - 12:54
*/ @Alias("clazz")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Clazz { private Integer id;
private String name;
private List<Student> students;
}

学生类

学生只能所属于一个班级之中,没有必要再关联

package main.java.cn.dai.pojo;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.apache.ibatis.type.Alias; /**
* @author ArkD42
* @file Mybatis
* @create 2020 - 05 - 30 - 12:55
*/ @Alias("student")
@NoArgsConstructor
@AllArgsConstructor
public class Student { private Integer id;
private String name; }

核心配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="mybatis.properties"/> <settings>
<!-- 日志实现 -->
<setting name="logImpl" value="LOG4J"/> <!-- 映射驼峰命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 开启懒加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings> <typeAliases>
<package name="cn.dai.pojo"/>
</typeAliases> <environments default="development"> <environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username:root}"/>
<property name="password" value="${password:123456}"/>
</dataSource>
</environment> </environments> <mappers>
<mapper resource="mapper/ClazzMapper.xml"/>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers> </configuration>

查询SQL

SELECT
*
FROM
t_clazz LEFT JOIN
t_student
ON
t_clazz.id = t_student.clazz_id
WHERE
t_clazz.id = #{id}

但是我们根本分不清哪个是班级表的字段,那个是学生表的字段

应该从表引用选中字段进

t_clazz.*,t_student.id stu_id,t_student.name stu_name,t_student.clazz_id

映射接口

package cn.dai.mapper;

import cn.dai.pojo.Clazz;

/**
* @author ArkD42
* @file Mybatis
* @create 2020 - 05 - 30 - 13:06
*/
public interface ClazzMapper { /**
* 根据班级ID查询,这个班级的信息,包括整个班级的全部学生的信息
* @param id
* @return
*/
Clazz queryClazzById(Integer id);
}

所以在映射器配置文件的配置是这样的

【使用集合表示一对多的多那个关系】

<?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接口名称-->
<mapper namespace="cn.dai.mapper.ClazzMapper"> <resultMap id="qcb" type="clazz">
<id column="id" property="id" />
<result column="name" property="name"/> <!--
声明的是集合,用的也就是集合 property 属性是集合的标识
ofType 该集合元素所属的泛型属性
-->
<collection property="students" ofType="student">
<id column="id" property="id"/>
<result column="name"property="name" />
</collection> </resultMap> <select id="queryClazzById" parameterType="int" resultMap="qcb" >
SELECT
t_clazz.*,t_student.id stu_id,t_student.name stu_name
FROM
t_clazz LEFT JOIN
t_student
ON
t_clazz.id = t_student.clazz_id
WHERE
t_clazz.id = #{id}
</select> </mapper>

测试类

import cn.dai.mapper.ClazzMapper;
import cn.dai.pojo.Clazz;
import cn.dai.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test; /**
* @author ArkD42
* @file Mybatis
* @create 2020 - 05 - 30 - 13:36
*/
public class OneToManyTest {
@Test
public void queryClazzById(){
SqlSession sqlSession = MybatisUtil.getSqlSession(true); ClazzMapper clazzMapper = sqlSession.getMapper(ClazzMapper.class); Clazz clazz = clazzMapper.queryClazzById(3); System.out.println(clazz); sqlSession.close();
}
}

对象成功返回,但是输出个这个。。

原因可能在集合字段绑定的属性不对

更改一下

应该是这个问题了,成功打印出来

一对多的懒加载查询

新增一个查询的方法

【按班级ID查询这个班级的信息,和里面学生无关】

Clazz queryClazzByIdForTwoStep(Integer id);

跟08结果集映射中的一样,对一对多也可以实现一个懒加载的查询

然后对学生表也要设置映射接口,编写二次查询的方法

【按班级ID查询这个班级所有学生】

package cn.dai.mapper;

import cn.dai.pojo.Student;

import java.util.List;

/**
* @author ArkD42
* @file Mybatis
* @create 2020 - 05 - 30 - 13:53
*/
public interface StudentMapper { List<Student> queryStudentsByClazzId(Integer id); }

映射器:

学生表只需要这样

<?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接口名称-->
<mapper namespace="cn.dai.mapper.StudentMapper"> <select id="queryStudentsByClazzId" resultType="cn.dai.pojo.Student" >
SELECT
*
FROM
t_student
WHERE
clazz_id = #{clazz_id}
</select> </mapper>

然后是班级表

    <resultMap id="qcb2" type="clazz">

        <!-- 同懒加载的查询一样,这个select属性的值写SQL查询 column传递值的列 -->
<collection
property="students"
column="id"
select="cn.dai.mapper.StudentMapper.queryStudentsByClazzId"
/> </resultMap> <select id="queryClazzByIdForTwoStep" parameterType="int" resultMap="qcb2" >
SELECT
id,name
FROM
t_clazz
WHERE
id = #{id}
</select>

测试类

import cn.dai.mapper.ClazzMapper;
import cn.dai.pojo.Clazz;
import cn.dai.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test; /**
* @author ArkD42
* @file Mybatis
* @create 2020 - 05 - 30 - 13:36
*/
public class OneToManyTest {
@Test
public void queryClazzById(){
SqlSession sqlSession = MybatisUtil.getSqlSession(true); ClazzMapper clazzMapper = sqlSession.getMapper(ClazzMapper.class); Clazz clazz = clazzMapper.queryClazzByIdForTwoStep(3); System.out.println(clazz); sqlSession.close();
}
}

测试结果

如果全打印出来,就会发现班级表的主键列空了,

似乎作为参数,就不再返回给对象了

在我们懒加载开启的情况下,但只调用班级对象本身,学生集合是不会查询的

比如我们只打印班级名称,就只会查询一个表

System.out.println(clazz.getName());

但是要查询ID还是会这样为NULL

这个问题要排查一下到底是为什么了

还是跟之前的那个问题一样,必须要声明主键,否则默认不返回给对象

【Mybatis官方不是说好的默认可以不写吗。。。】

然后查询就有结果了

双向关联查询实现

通过学生表来查询所在班级信息

类似上面的懒加载查询,这也需要分两次完成

要建立关联,首先学生表也需要一个来自班级表的关联

新增一个由学生表查询的方法

List<Student> queryStudentsByClazzIdForTwoStep(Integer clazz_id);

【学生分两次查询,第二次用于查询班级】

对应的学生映射器配置

    <resultMap id="rm01" type="student">
<id column="id" property="id"/>
<result column="name" property="name"/> <!--
因为一个clazz
对应的只是一个班级
select 调用的是前面的班级映射接口的方法
column 传递的参数是这个班级ID
-->
<association
property="clazz"
select="cn.dai.mapper.ClazzMapper.queryClazzByIdForTwoStep"
column="clazz_id"
/>
</resultMap> <select id="queryStudentsByClazzIdForTwoStep" resultMap="rm01" >
SELECT
id,name,clazz_id
FROM
t_student
WHERE
clazz_id = #{clazz_id}
</select>

同时班级映射器也需要更改一下

是对应调用结果查询,注释的方法是只按照班级ID查询

    <resultMap id="qcb2" type="clazz">
<id column="id" property="id"/> <!-- 同懒加载的查询一样,这个select属性的值写SQL查询 column传递值的列 -->
<collection
property="students"
column="id"
select="cn.dai.mapper.StudentMapper.queryStudentsByClazzIdForTwoStep"
/>
<!-- "cn.dai.mapper.StudentMapper.queryStudentsByClazzId"--> </resultMap> <select id="queryClazzByIdForTwoStep" parameterType="int" resultMap="qcb2" >
SELECT
id,name
FROM
t_clazz
WHERE
id = #{id}
</select>

测试类

    @Test
public void queryClazzById2(){
SqlSession sqlSession = MybatisUtil.getSqlSession(true); ClazzMapper clazzMapper = sqlSession.getMapper(ClazzMapper.class); Clazz clazz = clazzMapper.queryClazzByIdForTwoStep(3); System.out.println("班级名:" + clazz.getName() + "班级id:" + clazz.getName()); List<Student> students = clazz.getStudents(); for (Student student:students){
System.out.println("学号 " + student.getId() + "名字" + student.getName() +"所在班级" +student.getClazz().getName());
} sqlSession.close();
}

注意我们测试类调用是查询的班级的这个部分,如果调用这个班级对象,就会造成内存溢出

请看我这样调用

然后测试结果就会堆溢出

为什么会造成这种错误?

请回想之前我们为什么懒加载,分两次查询

因为如果不需要全部获取,那就只需要一部分的即可,也就是两次查询中的第一次

剩下的第二次,对象在不获取那个副表实体属性时,是不会调用的,这就是懒加载的神奇之处

所以这也就是为什么会堆溢出错误的原因

解决的办法有几种:

- 不调用toString,也就是不打印,就不会触发二次查询

- 设置结果集映射更改为结果集类型处理,但是一样会出现BUG,那就是空指针问题

【我不想因为一个BUG解决之后,还要再解决产生的第二个BUG,这么做不够优雅,所以第一个就行了】

【Mybatis】12 复杂关联查询的更多相关文章

  1. 三、mybatis多表关联查询和分布查询

    前言 mybatis多表关联查询和懒查询,这篇文章通过一对一和一对多的实例来展示多表查询.不过需要掌握数据输出的这方面的知识.之前整理过了mybatis入门案例和mybatis数据输出,多表查询是在前 ...

  2. mybatis一对多关联查询+pagehelper->分页错误

    mybatis一对多关联查询+pagehelper->分页错误. 现象: 网上其他人遇到的类似问题:https://segmentfault.com/q/1010000009692585 解决: ...

  3. MyBatis:一对一关联查询

    MyBatis从入门到放弃三:一对一关联查询 前言 简单来说在mybatis.xml中实现关联查询实在是有些麻烦,正是因为起框架本质是实现orm的半自动化. 那么mybatis实现一对一的关联查询则是 ...

  4. 7.mybatis一对多关联查询

    和第5节一对一查询类似,但是不同的是,一对一使用的是association,而一对多使用collection. 实例: 1个班级Class,对应1个老师Teacher,对应多个学生Student 1. ...

  5. 5.mybatis一对一表关联查询

    方式一:嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集,封装联表查询的数据(去除重复的数据)  SELECT * FROM class c,teacher t WHERE c.tid = t.t ...

  6. JAVA入门[9]-mybatis多表关联查询

    概要 本节要实现的是多表关联查询的简单demo.场景是根据id查询某商品分类信息,并展示该分类下的商品列表. 一.Mysql测试数据 新建表Category(商品分类)和Product(商品),并插入 ...

  7. mybatis多表关联查询之resultMap单个对象

    resultMap的n+1方式实现多表查询(多对一) 实体类 创建班级类(Clazz)和学生类(Student),并在Student中添加一个Clazz类型的属性,用于表示学生的班级信息. mappe ...

  8. mybatis一对多关联查询——(九)

    1.需求: 查询所有订单信息及订单下的订单明细信息. 订单信息与订单明细为一对多关系. 2.      sql语句 确定主查询表:订单表 确定关联查询表:订单明细表 在一对一查询基础上添加订单明细表关 ...

  9. MyBatis框架之关联查询

    概述:关联查询主要在<resultMap>元素中,用<association>配置一对一.用<collection> 配置一对多 一.一对一查询       1.使 ...

  10. mybatis多对多关联查询

    多对多关系 一个学生可以选多门课程,而一门课程可以由多个学生选择,这就是一个典型的多对多关联关系.所谓多对多关系,其实是由两个互反的一对多关系组成.即多对多关系都会通过一个中间表来建立,例如选课表.学 ...

随机推荐

  1. sqlite3自动插入创建时间和更新时间

    最近在记录一些简单的结构化日志信息时,用到了sqlite3数据库(保存的信息比较简单,用Mysql,SQL Server,Postgres这些数据库有点小题大做). 以前开发系统时,用Mysql和Po ...

  2. rust 程序设计笔记(2)所有权 & 引用

    所有权 数据存储在栈和堆上,存放在栈上的数据都是已知所占据空间的 突然的问题 // 内存中的栈是怎么存储数据的? 好的,想象一下你有一摞盘子.你只能从上面放盘子,也只能从上面拿盘子,这就是栈的工作方式 ...

  3. 非空处理 Java非空判断 非空处理及mysql数据库字段的not null

    1.mysql## 去掉非空,如果非空又没有默认值,这样程序在添加数据的时候i,如果没有设置值就会报错.该操作很危险.##ALTER TABLE `order_test` ADD COLUMN `te ...

  4. java+SpringCloud开发的性能和环保问题

    对于大部分商业应用开发程序员而言,使用java+spring是一件幸福的事情. 一般情况下,我们使用cloud开发不是那么重要.精密的应用,这些应用包括例如大型的商业交易,社区等等. 因为这些应用天然 ...

  5. 更难、更好、更快、更强:LLM Leaderboard v2 现已发布

    摘要 评估和比较大语言模型 (LLMs) 是一项艰巨的任务.我们 RLHF 团队在一年前就意识到了这一点,当时他们试图复现和比较多个已发布模型的结果.这几乎是不可能完成的任务:论文或营销发布中的得分缺 ...

  6. I2S 总线学习:1-有关概念

    背景 I2S总线 是一种常见的总线,也是需要掌握的. 概念 I2S(Inter-IC Sound)总线, 又称 集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准, ...

  7. Vs生成后 自动压缩 删除多余xml

    setlocal enabledelayedexpansionset ProjectName=$(ProjectName)del /s /q /f "$(ProjectDir)bin\Deb ...

  8. PySide6之多线程

    一.QThread 方法1:子类化创建多线程 创建子线程,继承自QThread类 在子线程中自定义信号 在子线程中重写 run() 方法,进行信号的触发 在主线程中实例化子线程 在主线程中对子线程的信 ...

  9. pymsql往数据库插入表情报错

    修改数据库 需要数据库支持utf8mb4 修改/etc/my.conf [client] default-character-set = utf8mb4 [mysql] default-charact ...

  10. 六.黑马程序员-eclipse的使用和快捷键

    1.Eclipse的概述 A: 是一个集成开发工具,专门针对java的 B: Eclipse 免费的 开源 C: MyEclipse 收费的 具体良好的插件扩展功能,针对插件收费2.Eclipse的使 ...