【Mybatis】12 复杂关联查询
一对多 & 多对一 关联查询
数据库准备:
一个班级表,字段:班级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,这么做不够优雅,所以第一个就行了】