企业培训笔记: Mybatis 懒加载机制

发布于:2025-07-11 ⋅ 阅读:(14) ⋅ 点赞:(0)


MyBatis 懒加载机制简介

MyBatis 的懒加载(Lazy Loading) 是一种优化策略,允许在需要关联对象时再进行数据库查询,而非在主查询时立即加载所有关联数据。这种机制特别适用于一对多或多对多关系,可以显著提升查询性能。

一、核心概念与原理

1. 何时使用懒加载

  • 当查询主对象(如 User)时,其关联对象(如 Order 列表)可能不会立即使用
  • 通过懒加载,主对象查询和关联对象查询被分离,减少初始查询的数据量

2. 实现原理

  • MyBatis 通过动态代理生成关联对象的代理类
  • 当首次访问关联对象的方法时,触发实际的数据库查询
  • 底层依赖 ResultMap 的嵌套配置和 association/collection 标签

二、懒加载配置步骤

1. 全局配置

mybatis-config.xml配置文件

<settings>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="aggressiveLazyLoading" value="false"/> <!-- 按需加载 -->
</settings>

或在 Spring Boot 中配置:
application.yml配置文件

mybatis:
  configuration:
    lazy-loading-enabled: true
    aggressive-lazy-loading: false

application.properties配置文件

# 开启懒加载(必须设置)
mybatis.configuration.lazy-loading-enabled=true

# 禁用积极懒加载(按需加载,推荐设置)
mybatis.configuration.aggressive-lazy-loading=false

2. ResultMap 配置

使用 select 属性指定子查询,实现延迟加载:

<resultMap id="userWithOrdersMap" type="User">
  <id property="id" column="id"/>
  <result property="name" column="name"/>
  
  <!-- 一对多关联,使用 select 指定延迟加载的查询 -->
  <collection 
    property="orders" 
    ofType="Order"
    column="id"
    select="getOrdersByUserId"/>
</resultMap>

<select id="getOrdersByUserId" resultType="Order">
  SELECT * FROM orders WHERE user_id = #{userId}
</select>

三、懒加载的优缺点

优点

  1. 减少初始查询数据量:避免一次性加载所有关联数据
  2. 提升响应速度:主对象查询更快返回
  3. 节省内存:不需要立即加载所有数据到内存

缺点

  1. 可能导致 N+1 查询问题:主查询 1 次 + 关联查询 N 次
  2. 事务边界问题:懒加载可能在事务外触发,导致会话已关闭
  3. 调试复杂性:延迟加载的时机可能不直观

四、懒加载的使用场景

1. 一对多/多对多关系

  • 查询用户时不立即加载其所有订单
  • 查询文章时不立即加载所有评论

2. 复杂对象图

  • 嵌套层级深的对象结构
  • 某些关联数据使用频率低

3. 性能敏感场景

  • 移动端或网络带宽有限的环境
  • 大数据量查询

五、懒加载与 Session 生命周期

1. 关键注意点

  • 懒加载查询发生在原始会话关闭后会抛出异常
  • 需要确保会话在懒加载触发时仍然有效

2. 解决方案

  • 使用 OpenSessionInView 模式(Web 应用)
  • 在 Service 层显式控制 Session 生命周期
  • 使用 FetchType.EAGER 对特定关联强制立即加载

六、MyBatis-Plus 中的懒加载

在 MyBatis-Plus 中,懒加载配置与原生 MyBatis 一致,但需要额外注意:

@TableName("user")
public class User {
    private Long id;
    private String name;
    
    // 标记为 transient 避免序列化问题
    private transient List<Order> orders;
    
    // getters/setters
}

MyBatis 懒加载实操

一,创建ClassInfo实体类

在这里插入图片描述

package net.army.entity;

import lombok.Data;

import java.util.List;

/**
 * 功能:班级实体类
 * 日期:2025年07月03日
 * 作者:梁辰兴
 */
@Data
public class ClassInfo {
    private Integer cid;
    private String cname;

    // 声明学生对象属性集合
    private List<Student> students;
}

二,创建Student实体类

在这里插入图片描述

package net.army.entity;

import lombok.Data;

/**
 * 功能:学生实体类
 * 日期:2025年07月03日
 * 作者:梁辰兴
 */
@Data
public class Student {
    private Integer sid;
    private String sname;
    private Integer cid;
    private Integer age;
}

三,创建ClassInfoMapper映射器接口

在这里插入图片描述

package net.army.mapper;

import net.army.entity.ClassInfo;
import org.apache.ibatis.annotations.Mapper;

/**
 * 功能:班级映射器接口
 * 日期:2025年07月03日
 * 作者:梁辰兴
 */
@Mapper
public interface ClassInfoMapper {
    // 查询班级信息(包含该班级所有学生的信息)
    ClassInfo queryClassStudentInfo(int cid);
}

四,创建ClassInfoMapper.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="net.army.mapper.ClassInfoMapper">
    <!--
    定义resultMap结果集
    -->
    <resultMap id="classStudentMap" type="net.army.entity.ClassInfo">
        <id column="cid" property="cid"></id>
        <result column="cname" property="cname"></result>
        <!--
        select : 指定需要关联执行的sql语句的id,
                 封装集合属性stus中的对象数据需要执行的sql语句对应select标签id
        column :需要关联执行的sql语句where条件由先执行的sql语句中那个列确定
        -->
        <collection property="students"
                    ofType="net.army.entity.Student"
                    select="classStudentList"
                    column="cid">
            <id property="sid" column="sid"></id>
            <result column="sname" property="sname"></result>
            <result column="age" property="age"></result>
        </collection>
    </resultMap>
    <!--查询班级信息-->
    <select id="queryClassStudentInfo" parameterType="java.lang.Integer"
            resultMap="classStudentMap">
        select * from class where cid=#{cid}
    </select>
    <!--查询某个班级下的学生信息-->
    <select id="classStudentList" resultType="net.army.entity.Student"
            parameterType="java.lang.Integer">
        select * from student where cid=#{cid}
    </select>
</mapper>

五,配置application.properties配置文件

在这里插入图片描述

# 应用服务 WEB 访问端口
server.port=8081
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mapper/*xml
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=your_username
spring.datasource.password=your_password

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

#开启mybatis的懒加载
mybatis.configuration.lazy-loading-enabled=true
mybatis.configuration.aggressive-lazy-loading=false

六,启动类添加映射器包扫描注解

在这里插入图片描述

package net.army;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("net.army.mapper")
@SpringBootApplication
public class SpringBootMyBatisDay04Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootMyBatisDay04Application.class, args);
    }

}

七,测试懒加载

1,在测试类SpringBootMyBatisDay04ApplicationTests,中添加测试方法。
在这里插入图片描述

package net.army;

import net.army.entity.ClassInfo;
import net.army.entity.Student;
import net.army.mapper.ClassInfoMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class SpringBootMyBatisDay04ApplicationTests {

    @Autowired
    private ClassInfoMapper classInfoMapper;

    @Test
    public void queryClassStudentTest(){
        ClassInfo classes = classInfoMapper.queryClassStudentInfo(1);
        System.out.println(classes.getCid()+"\t"+classes.getCname());

        //获得班级下的所有学生信息
        List<Student> students = classes.getStudents();
        for(Student s:students){
            System.out.println(s);
        }
    }
}

2,运行测试方法queryClassStudentTest()。
在这里插入图片描述
在这里插入图片描述
3,查看运行结果,可以发现查询时(1:n),是先查询1方的记录,然后再根据1方的记录,查询n方的记录,这样可以改善查询的效率。
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到