MyBatis 延迟加载

1. 什么是延迟加载

​ 延迟加载也称为懒加载、惰性加载,使用延迟加载可以提高程序的运行效率,针对数据持久层的操作,在某些特定查询的情况下去访问特定的数据库,在其他情况下可以不访问某些数据表,尽量减少 SQL 的执行,从而达到提高速度的目的,是对数据库操作的一种优化。

说明:延迟加载只存在于数据表的级联查询中,单表查询没有延迟加载的功能。

具体的业务场景:数据库中有班级表(Class)和学生表(Student),当我们查询 Student 对象时,因为有级联关系,所以会将该 student 对象关联的 class 对象一并查询出来,这样就需要级联查询两张数据表的信息。

在这里插入图片描述

延迟加载的思路是:当我们查询 Student 对象时,如果只用到了 name 或 GPA 的字段信息,没有调用 class 属性,则只发送一条 SQL 语句查询 Student 表;如果需要调用 Class 的属性,则发送两条 SQL 语句查询 Student 和 Class。

2. 测试环境准备

Step1:创建 student 和 class 数据表

# student表
create table class
(
    id int auto_increment primary key,
    name varchar(22) null
);
# class表
create table student
(
    id int auto_increment primary key,
    name varchar(25) not null comment '姓名',
    GPA double default 0 null comment '分数',
    cid int null
);

Step2:创建 Student 和 Class 实体类

package com.training.entity;
import lombok.Data;
import java.util.List;

@Data
public class Class {
    private Integer id;
    private String name;
    private List<Student> students;
}
package com.training.entity;
import lombok.Data;

@Data
public class Student {
    private Integer id;
    private String name;
    private Double GPA;
    private Class aClass;
}

Step3:在 config.xml 中添加配置信息

<?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>
    <!--数据源-->
    <environments default="dev">
        <!--可以配置多个环境-->
        <environment id="dev">
            <!--配置JDBC事务管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- POOLED配置JDBC数据库连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8"></property>
                <property name="username" value="root"></property>
                <property name="password" value="123456"></property>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="com/training/mapper/StudentMapper.xml"></mapper>
        <mapper resource="com/training/mapper/ClassMapper.xml"></mapper>
    </mappers>

</configuration>

Step4:自定义 StudentMapper 接口

package com.training.mapper;

import com.training.entity.Student;

public interface StudentMapper {
    public Student findById(Integer id);
}

Step5:SQL 配置文件 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.training.mapper.StudentMapper">

    <resultMap id="studentMap" type="com.training.entity.Student">
        <id column="sid" property="id"></id>
        <result column="sname" property="name"></result>
        <result column="GPA" property="GPA"></result>
        <association property="aClass" javaType="com.training.entity.Course">
            <id column="cid" property="id"></id>
            <result column="cname" property="name"></result>
        </association>
    </resultMap>
    
    <select id="findById" parameterType="java.lang.Integer" resultMap="studentMap">
        select s.id sid, s.name sname, GPA, c.id cid, c.name cname
        from student s,class c
        where s.cid = c.id and s.id = 1;
    </select>
</mapper>

Step6:编写 Test 测试类

package com.training.test;

import com.training.entity.Class;
import com.training.entity.Student;
import com.training.entity.User;
import com.training.mapper.ClassMapper;
import com.training.mapper.CourseMapper;
import com.training.mapper.StudentMapper;
import com.training.mapper.UserMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        //加载MyBatis配置信息
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        //构建SQLSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new
                SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = factory.openSession();
        //获取实现接口的代理对象
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        System.out.println(mapper.findById(1));
    }
}

在 config.xml 中配置 MyBatis 打印 SQL 设置

<settings>
    <!-- 打印SQL -->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

控制台的打印信息如下:

在这里插入图片描述


3. 开启延迟加载

在 config.xml 配置文件中开启延迟加载

<settings>
    <!-- 打印SQL -->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!-- 开启延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"></setting>
</settings>

将多表关联查询拆分成多个单表查询

1、自定义接口 StudentMapper.java

public interface StudentMapper {
    public Student findById(Integer id);
}

2、SQL 配置文件 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.training.mapper.StudentMapper">

    <resultMap id="studentMapLazy" type="com.training.entity.Student">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="GPA" property="GPA"></result>
        <association property="aClass" javaType="com.training.entity.Class" select="com.training.mapper.ClassMapper.findByClassId" column="cid">
        </association>
    </resultMap>
    
    <select id="findById" parameterType="java.lang.Integer" resultMap="studentMapLazy">
        select * from student where id=#{id};
    </select>
</mapper>

3、自定义 ClassMapper.java

public interface ClassMapper {
    public Class findByClassId(Integer id);
}

4、SQL 配置文件 ClassMapper.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.training.mapper.ClassMapper">

    <resultMap id="classMapLazy" type="com.training.entity.Class">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
    </resultMap>
    
    <select id="findByClassId" parameterType="java.lang.Integer" resultMap="classMapLazy">
       select * from class where id=#{id};
    </select>
</mapper>

5、重新执行上面的测试类,控制台打印的结果如下:

在这里插入图片描述

同样的查询结果,但是 SQL 语句的执行方式不同,那么这样处理有什么特别的作用呢?

例1:根据学生的 id 查询 student 对象的 name 值。

public class Test {
    public static void main(String[] args) {
        //加载MyBatis配置信息
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        //构建SQLSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new
                SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = factory.openSession();
        //获取实现接口的代理对象
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.findById(1);
        System.out.println(student.getName());
    }
}

在这里插入图片描述

可以惊奇地发现,此时 SQL 语句只执行了一次。如果只需要获取 student 的信息,那么只执行第一条 SQL 就足够了。

但是如果需求是根据学生的 id 查询该学生的班级名 cname,即要获取 Class 表的级联信息,那么一条 SQL 语句就不够用了,此时需要再去执行第二条 SQL 语句,这就是按需加载。

public class Test {
    public static void main(String[] args) {
        //加载MyBatis配置信息
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        //构建SQLSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new
                SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = factory.openSession();
        //获取实现接口的代理对象
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.findById(1);
        System.out.println(student.getAClass().getName());
    }
}

在这里插入图片描述


4. 总结

MyBatis 框架的延迟加载,是实际开发中使用频率较高的功能,正确的使用延迟加载,可以有效减少 Java 程序与数据库交互次数,从而提升整个系统的运行效率,延迟加载适用于多表关联查询的业务场景,而单表查询本身只涉及到一张数据表的查询,所以也没有优化的余地了。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐