zoukankan      html  css  js  c++  java
  • 【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,这么做不够优雅,所以第一个就行了】

  • 相关阅读:
    Python_Excel文件操作
    Python_CRC32
    Python_替换当前目录下文件类型
    Python_os、os.path、os.shutil使用案例
    Python_文件与文件夹操作
    MyBatis/Ibatis中#和$的区别
    遍历listmap 遍历map
    jquery操作select(取值,设置选中)
    ==与===区别(两个等号与三个等号)
    常用map总结
  • 原文地址:https://www.cnblogs.com/mindzone/p/12992223.html
Copyright © 2011-2022 走看看