0%

Mybatis高级映射及延迟加载

  • 高级映射:将多表查询结果映射为 Java 类对象
    • 多对一高级映射
    • 一对多高级映射

十三、★MyBatis的高级映射及延迟加载

  • 高级映射:将多表查询结果映射为 Java 类对象

  • 准备工作:

    • 模块名:mybatis-010-advanced-mapping
    • 打包方式:jar
    • 引入依赖:mysql驱动依赖、mybatis依赖、logback依赖、junit依赖。
    • 引入配置文件:jdbc.propertiesmybatis-config.xmllogback.xml
    • 创建 pojo 类:StudentClazz
    • 创建 Mapper 接口:com.f.mybatis.mapper.StudentMappercom.f.mybatis.mapper.ClazzMapper
    • 创建 Mapper 接口对应的映射文件:com/f/mybatis/mapper/StudentMapper.xmlcom/f/mybatis/mapper/ClazzMapper.xml
    • 创建单元测试:StudentMapperTestClazzMapperTest
    • 创建工具类:SqlSessionUtil

13.1 多对一

  • 多对一:多个学生对应一个班级。

    • 多的一方:学生表(主表)
    • 一的一方:班级表(副表)

    既然学生表是主表,那么 JVM 中的主对象就是 Student 对象、Clazz 对象是副对象。

    • 因此在 Student 类中,添加成员变量:private Clazz clazz;

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-SSM-notebook/img/Mybatis/多对一.png)

  • 怎么分主表和副表?

    • 原则:谁在前谁是主表。
      • 多对一:多在前,那么多就是主表。
      • 一对多:一在前,那么一就是主表。
  • 如何将 SQL 语句多表查询的结果映射为 Student 类对象?

    • Student 类中含有 Clazz 类成员变量。

    可以有多种方式,常见的包括三种:

    • 第一种方式:一条SQL语句,级联属性映射。
    • 第二种方式:一条SQL语句,使用 association 标签。
    • 第三种方式:两条 SQL 语句,分步查询(这种方式常用:优点一是可复用,优点二是支持懒加载,提高了性能)
      • 延迟加载的核心原理是:用的时候再执行查询语句,不用的时候不查询。

13.1.1 级联属性映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.f.mybatis.mapper;

import com.f.mybatis.pojo.Student;

/**
* @author fzy
* @date 2024/1/11 16:28
*/
public interface StudentMapper {
/**
* 根据id获取学生信息,同时获取学生关联的班级信息
*
* @param id
* @return
*/
Student selectById(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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="com.f.mybatis.mapper.StudentMapper">
<!--多对一映射的第一种方式:一条SQL语句,级联属性映射-->
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<result property="clazz.cid" column="cid"/>
<result property="clazz.cname" column="cname"/>
</resultMap>
<select id="selectById" resultMap="studentResultMap">
SELECT
s.sid, s.sname, c.cid, c.cname
FROM
t_stu s, t_clazz c
<where>
s.sid = #{sid} AND s.cid = c.cid
</where>
</select>
</mapper>
1
2
3
4
5
6
7
8
@Test
public void testSelectById() {
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectById(1);
System.out.println(student);
SqlSessionUtil.close(sqlSession);
}

13.1.2 association标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.f.mybatis.mapper;

import com.f.mybatis.pojo.Student;

/**
* @author fzy
* @date 2024/1/11 16:28
*/
public interface StudentMapper {
/**
* 根据id获取学生信息,同时获取学生关联的班级信息,使用association标签
*
* @param id
* @return
*/
Student selectByIdAssociation(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?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="com.f.mybatis.mapper.StudentMapper">
<!--多对一映射的第二种方式:一条SQL语句,association标签-->
<resultMap id="studentResultMapAssociation" type="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
<!--
association翻译为关联。一个Student对象关联一个Clazz对象
property:提供要映射的POJO类的属性名
javaType:用来指定要映射的java类型
下面表示要映射Student类中的clazz属性,要将该属性映射为Clazz类
-->
<association property="clazz" javaType="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
</association>
</resultMap>
<select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
SELECT
s.sid, s.sname, c.cid, c.cname
FROM
t_stu s, t_clazz c
<where>
s.sid = #{sid} AND s.cid = c.cid
</where>
</select>
</mapper>
1
2
3
4
5
6
7
8
@Test
public void testSelectByIdAssociation() {
SqlSession sqlSession = SqlSessionUtil.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectByIdAssociation(1);
System.out.println(student);
SqlSessionUtil.close(sqlSession);
}
  • 第二种方式和第一种方式差别不大,就是在 StudentMapper.xml 文件中增加了 association 标签。

13.1.3 ★分步查询(常用)

  • 分步查询是指,分两步对两个表进行查询。因为学生表是主表,所以肯定是先查学生表。

    • 第一步:先查学生表,根据学生 id 查学生信息。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      package com.f.mybatis.mapper;

      import com.f.mybatis.pojo.Student;

      /**
      * @author fzy
      * @date 2024/1/11 16:28
      */
      public interface StudentMapper {
      /**
      * 分步查询第一步,根据学生id查学生信息
      *
      * @param id
      * @return
      */
      Student selectByIdStep1(Integer id);
      }
      • 注意上面这是 StudentMapper 接口。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      <?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="com.f.mybatis.mapper.StudentMapper">
      <!--两条SQL语句,完成多对一的分布查询-->
      <!--这里是第一步,根据学生的id查询学生的所有信息,这些信息中含有班级的id(cid)-->
      <resultMap id="studentResultMapByStep" type="Student">
      <id property="sid" column="sid"/>
      <result property="sname" column="sname"/>
      <!--
      select里面指定另外第二步SQL语句的sqlId(namespace+id)
      column里面指定传给第二步SQL语句的参数(是第一步SQL语句查询结果里面的)
      -->
      <association property="clazz"
      select="com.f.mybatis.mapper.ClazzMapper.selectByIdStep2"
      column="cid"/>
      </resultMap>
      <select id="selectByIdStep1" resultMap="studentResultMapByStep">
      SELECT sid, sname, cid
      FROM t_stu
      <where>
      sid = #{sid}
      </where>
      </select>
      </mapper>
      • 注意上面 resultMap 中的配置。

        1
        2
        3
        4
        5
        6
        7
        <!--
        select里面指定另外第二步SQL语句的sqlId(namespace+id)
        column里面指定传给第二步SQL语句的参数(是第一步SQL语句查询结果里面的)
        -->
        <association property="clazz"
        select="com.f.mybatis.mapper.ClazzMapper.selectByIdStep2"
        column="cid"/>
    • 第二步:再查询班级表,根据班级 id 查班级信息(班级 id 是第一步查询传过来的)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      package com.f.mybatis.mapper;

      import com.f.mybatis.pojo.Clazz;

      /**
      * @author fzy
      * @date 2024/1/11 16:40
      */
      public interface ClazzMapper {
      /**
      * 分步查询第二步,根据班级id查班级信息
      *
      * @param id
      * @return
      */
      Clazz selectByIdStep2(Integer id);
      }
      • 注意上面这是 ClazzMapper 接口。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <?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="com.f.mybatis.mapper.ClazzMapper">
      <select id="selectByIdStep2" resultType="Clazz">
      SELECT cid, cname
      FROM t_clazz
      <where>
      cid = #{cid}
      </where>
      </select>
      </mapper>
    • 第三步,进行测试。

      1
      2
      3
      4
      5
      6
      7
      8
      @Test
      public void testSelectByIdStep1() {
      SqlSession sqlSession = SqlSessionUtil.openSession();
      StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
      Student student = mapper.selectByIdStep1(5);
      System.out.println(student);
      SqlSessionUtil.close(sqlSession);
      }

      发现执行结果确实是执行了两条 SQL 语句:

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-SSM-notebook/img/Mybatis/多对一分布查询.png)

  • 分步查询的优点:

    • 第一个优点:代码复用性增强。
    • 第二个优点:支持延迟加载(懒加载)【暂时访问不到的数据可以先不查询,提高程序的执行效率】。

13.2 多对一延迟加载

  • 要想使用延迟加载,非常简单,只需要在分布查询的 association 标签中添加 fetchType="lazy" 即可。即修改 13.1.3 小节的 StudentMapper.xml 文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ...
    <resultMap id="studentResultMapByStep" type="Student">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <!--
    select里面指定另外第二步SQL语句的sqlId(namespace+id)
    column里面指定传给第二步SQL语句的参数(是第一步SQL语句查询结果里面的)
    fetchType="lazy"表示开启延迟加载(懒加载)
    -->
    <association property="clazz"
    select="com.f.mybatis.mapper.ClazzMapper.selectByIdStep2"
    column="cid"
    fetchType="lazy"/>
    </resultMap>
    ...

    注意:默认情况下是不开启延迟加载的。

  • 上面的这种 fetchType="lazy" 配置,是局部的设置,只对当前的 association 关联的 sql 语句起作用。

    如果想开启全局的延迟加载,需要在 mybatis-config.xml 中,进行 setting 配置:

    <setting name="lazyLoadingEnabled" value="true"/>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?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>
    ...
    <settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
    ...
    </configuration>
    • 在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全局的延迟加载机制。
  • 实际开发中的模式:

    • 把全局的延迟加载打开:<setting name="lazyLoadingEnabled" value="true"/>
    • 如果某一步不需要使用延迟加载,就通过 fetchType="eager" 进行设置。

13.3 一对多

  • 一对多:一个班级对应多个学生。

    • 一的一方:班级表(主表)
    • 多的一方:学生表(副表)

    既然班级表是主表,那么 JVM 中的主对象就是 Clazz 对象、Student 对象是副对象。

    • 因此在 Clazz 类中,添加成员变量:private List<Student> students;

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-SSM-notebook/img/Mybatis/一对多.png)

  • 如何将 SQL 语句多表查询的结果映射为 Clazz 类对象?

    • Clazz 类中含有 List<Student> 成员变量。

    可以有多种方式,常见的包括两种:

    • 第一种方式:collection
    • 第二种方式:分步查询。

13.3.1 collection标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.f.mybatis.mapper;

import com.f.mybatis.pojo.Clazz;

/**
* @author fzy
* @date 2024/1/11 16:40
*/
public interface ClazzMapper {
/**
* 根据id查询班级信息,同时获得相关联的学生信息
*
* @param id
* @return
*/
Clazz selectByIdCollection(Integer id);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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="com.f.mybatis.mapper.ClazzMapper">
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"/>
<result property="cname" column="cname"/>
<!--一对多,使用collection,collection是集合的意思-->
<!--ofType属性用来指定集合中的元素类型-->
<collection property="students" ofType="Student">
<id property="sid" column="sid"/>
<result property="sname" column="sname"/>
</collection>
</resultMap>
<select id="selectByIdCollection" resultMap="clazzResultMap">
SELECT c.cid, c.cname, s.sid, s.sname
FROM t_clazz c, t_stu s
<where>
c.cid = s.cid AND c.cid = #{cid}
</where>
</select>
</mapper>
1
2
3
4
5
6
7
8
@Test
public void testSelectByIdCollection() {
SqlSession sqlSession = SqlSessionUtil.openSession();
ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
Clazz clazz = mapper.selectByIdCollection(1000);
System.out.println(clazz);
SqlSessionUtil.close(sqlSession);
}

13.3.2 ★分步查询(常用)

  • 一对多的分步查询类似于多对一的分布查询,因为此时班级表是主表,所以肯定是先查班级表。

    • 第一步:先查班级表,根据班级 id 查班级信息。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      package com.f.mybatis.mapper;

      import com.f.mybatis.pojo.Clazz;

      /**
      * @author fzy
      * @date 2024/1/11 16:40
      */
      public interface ClazzMapper {
      /**
      * 一对多分步查询第一步,根据班级id查班级信息
      *
      * @param id
      * @return
      */
      Clazz selectByIdStep1(Integer id);
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <?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="com.f.mybatis.mapper.ClazzMapper">
      <resultMap id="clazzResultMapByStep" type="Clazz">
      <id property="cid" column="cid"/>
      <result property="cname" column="cname"/>
      <collection property="students"
      select="com.f.mybatis.mapper.StudentMapper.selectByIdStep2"
      column="cid"/>
      </resultMap>
      <select id="selectByIdStep1" resultMap="clazzResultMapByStep">
      SELECT cid, cname
      FROM t_clazz
      <where>
      cid = #{cid}
      </where>
      </select>
      </mapper>
    • 第二步:再查学生表,根据班级 id 查学生信息(班级 id 是第一步查询传过来的)。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      package com.f.mybatis.mapper;

      import com.f.mybatis.pojo.Student;

      /**
      * @author fzy
      * @date 2024/1/11 16:28
      */
      public interface StudentMapper {
      /**
      * 一对多分步查询第二步,根据班级id查学生信息
      *
      * @param cid
      * @return
      */
      Student selectByIdStep2(Integer cid);
      }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <?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="com.f.mybatis.mapper.StudentMapper">
      <select id="selectByIdStep2" resultType="Student">
      SELECT sid, sname
      FROM t_stu
      <where>
      cid = #{cid}
      </where>
      </select>
      </mapper>
    • 第三步,进行测试。

      1
      2
      3
      4
      5
      6
      7
      8
      @Test
      public void testSelectByIdStep1() {
      SqlSession sqlSession = SqlSessionUtil.openSession();
      ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
      Clazz clazz = mapper.selectByIdStep1(1000);
      System.out.println(clazz);
      SqlSessionUtil.close(sqlSession);
      }
  • 一对多分步查询 和 多对一分步查询非常类似,区别在于:

    • 一对多分步查询使用的是 collection 标签。
    • 多对一分步查询使用的是 association 标签。

13.4 一对多延迟加载

  • 和多对一延迟加载一样,具体看 13.2 小节笔记。
---------------The End---------------