【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,这么做不够优雅,所以第一个就行了】

posted @ 2020-05-30 15:47  emdzz  阅读(233)  评论(0编辑  收藏  举报