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>
三、懒加载的优缺点
优点
- 减少初始查询数据量:避免一次性加载所有关联数据
- 提升响应速度:主对象查询更快返回
- 节省内存:不需要立即加载所有数据到内存
缺点
- 可能导致 N+1 查询问题:主查询 1 次 + 关联查询 N 次
- 事务边界问题:懒加载可能在事务外触发,导致会话已关闭
- 调试复杂性:延迟加载的时机可能不直观
四、懒加载的使用场景
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方的记录,这样可以改善查询的效率。