MyBatis 的高级映射及延迟加载

43 阅读3分钟

MyBatis 的高级映射及延迟加载

1. 准备工作

  1. 新建一个模块
  2. 配置 pom 文件
  3. 打包方式:<packaging>jar</packaging>
  4. 导入依赖:mybatis mysql junit logback lombok
  5. 拷贝工具类 SqlSessionUtil:
package com.dsl.mybatis.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

/**
 * MyBatis工具类
 *
 * @author DSL
 * @version 1.0
 * @since 1.0
 */
public class SqlSessionUtil {
    private static SqlSessionFactory sqlSessionFactory;

    /**
     * 类加载时初始化sqlSessionFactory对象
     */
    static {
        try {
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();

    /**
     * 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。
     *
     * @return 新的会话对象
     */
    public static SqlSession openSession() {
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession();
            local.set(sqlSession);
        }
        return sqlSession;
    }

    /**
     * 关闭SqlSession对象
     * @param sqlSession
     */
    public static void close(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.close();
        }
        local.remove();
    }
}
  1. 准备数据库:t_student t_clazz

2. 多对一(多个学生对应一个班级)

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

  • 第一种方式:一条SQL语句,级联属性映射。
  • 第二种方式:一条SQL语句,association。
  • 第三种方式:两条SQL语句,分步查询。(这种方式常用:优点一是可复用。优点二是支持懒加载。)

2.1 级联属性映射

在 Student 中添加一个属性:Clazz ,表示学生关联的班级对象

在 StudentMapper.xml 文件编写如下代码,这里再 idea 中可能会标红,但是不影响查询结果

<?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.dsl.mybatis.mapper.StudentMapper">
    <resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"/>
        <result property="studentName" column="student_name"/>
        <result property="clazz.cid" column="cid"/>
        <result property="clazz.clazzName" column="clazz_name"/>
    </resultMap>

    <select id="selectStudentAndClazz" resultMap="studentResultMap">
        select ts.sid, ts.student_name, ts.cid, tc.clazz_name
        from t_student ts
                 join t_clazz tc on ts.cid = tc.cid
        where ts.sid = #{sid};
    </select>
</mapper>

在 StudentMapper 中添加对应的接口:Student selectStudentAndClazz(Integer id);

最后编写测试类对代码进行测试:

public class TestMapper {
    @Test
    public void testSelectStudentAndClazz(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectStudentAndClazz(1);
        System.out.println(student);
    }
}

观察结果:

Student(sid=1, studentName=zs, clazz=Clazz(cid=101, clazzName=一班))成功查询了所有数据

2.2 association

使用该方式其他条件不用更改,只需要改变 StudentMapper.xml中的 中对应的内容即可

更改如下:

<?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.dsl.mybatis.mapper.StudentMapper">

    <resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"/>
        <result property="studentName" column="student_name"/>
        <association property="clazz" javaType="Clazz">
            <id property="cid" column="cid"/>
            <result property="clazzName" column="clazz_name"/>
        </association>
    </resultMap>

    <select id="selectStudentAndClazz" resultMap="studentResultMap">
        select ts.sid, ts.student_name, ts.cid, tc.clazz_name
        from t_student ts
                 join t_clazz tc on ts.cid = tc.cid
        where ts.sid = #{sid};
    </select>
</mapper>

再次使用上述测试代码进行测试,得到相同结果

2.3 分布查询

分布查询本质上就是通过两步查询出所有数据:第一步是先通过学生 id 查询出学生的所有信息(学生id,学生姓名name,学生班级号cid),然后通过班级号 cid 查询出学生的班级信息,最后封装结果返回。

  1. 编写 StudentMapper.xml和对应的接口,在 association 中 select 位置填写 sqlId。sqlId = namespace + id。其中 column 属性作为这条子 sql 语句的条件。
<?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.dsl.mybatis.mapper.StudentMapper">

    <resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"/>
        <result property="studentName" column="student_name"/>
        <association property="clazz" select="com.dsl.mybatis.mapper.ClazzMapper.selectByCid" column="cid"/>
    </resultMap>

    <select id="selectStudentAndClazz" resultMap="studentResultMap">
        select sid, student_name, cid
        from t_student
        where sid = #{sid};
    </select>
</mapper>

StudentMapper接口中添加对应的方法 Student selectStudentAndClazz(Integer id);

  1. 编写 ClazzMapper.xml和对应的 mapper 接口
<?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.dsl.mybatis.mapper.ClazzMapper">
    <resultMap id="BaseResultMap" type="com.dsl.mybatis.pojo.Clazz">
        <id column="cid" jdbcType="INTEGER" property="cid"/>
        <result column="clazz_name" jdbcType="VARCHAR" property="clazzName"/>
    </resultMap>
    <select id="selectByCid" resultMap="BaseResultMap">
        select cid, clazz_name
        from t_clazz
        where cid = #{cid};
    </select>
</mapper>

并在对应接口上添加 Clazz selectByCid(Integer cid);方法

  1. 执行结果,可以观察到两条语句先后执行

  1. 使用该方法的优点:
  • 代码复用性增强
  • 支持延迟加载。【暂时访问不到的数据可以先不查询。提高程序的执行效率。】

2.4 多对一的延迟加载

要想支持延迟加载,只需要在分布查询的基础上在 association 标签中添加 fetchType="lazy" 即可。

添加后如果我们只需要查询出部分信息,如上面测试程序的学生姓名,可以通过控制台看出只有一天语句执行。

    @Test
    public void testSelectStudentAndClazz(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectStudentAndClazz(1);
        System.out.println(student.getStudentName());
    }

如果后续需要使用到学生班级名称,这时候会执行相关的 sql 语句,通过控制台可以看出关联的语句执行了,这就是延迟加载。

上面开启的是局部延迟加载,我们可以通过配置 mybatis-config.xml 文件开启全局延迟加载,具体代码如下:

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

把之前加的 fetchType="lazy" 测试,我们会看到相同的结果。

开启全局延迟加载之后,所有的 sql 都会支持延迟加载,如果某个 sql 你不希望它支持延迟加载怎么办呢?将 fetchType 设置为 eager。

3. 一对多(一个班级对应多个学生)

一对多的实现,通常是在一的一方中有List集合属性。

在Clazz类中添加 List<Student> stus; 属性。这里需要去掉之前在 Student 类中加入 Clazz 属性。

一对多的实现通常包括两种实现方式:

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

3.1 collection

  1. 编写 ClazzMapper.xml和对应Mapper接口
<?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.dsl.mybatis.mapper.ClazzMapper">
    <resultMap id="resultMap" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="clazzName" column="clazz_name"/>
        <collection property="students" ofType="student">
            <id property="sid" column="sid"/>
            <result property="studentName" column="student_name"/>
        </collection>
    </resultMap>

    <select id="selectClazzAndStudentsByCid" resultMap="resultMap">
        select *
        from t_clazz tc
                 join t_student ts on tc.cid = ts.cid
        where tc.cid = #{cid};
    </select>
</mapper>

这里ofType,表示的是集合中的类型