MyBatis 面试里最核心的问题有两个:一是 SQL 到底怎么执行,二是关联对象为什么能延迟加载。它们其实在同一条链路上:MyBatis 先把配置和 SQL 映射解析成对象,执行查询时再完成参数映射、SQL 执行和结果封装。

MyBatis 执行流程总览

在这里插入图片描述

读取 mybatis-config
配置

解析 Mapper
映射文件

构建
SqlSessionFactory

创建
SqlSession

获取 Mapper
代理对象

调用 Mapper 方法

找到
MappedStatement

Executor
执行 SQL

参数映射
SQL 执行
结果映射

返回 Java 对象

一次查询大致会经过这些组件:

组件 作用
mybatis-config.xml MyBatis 全局配置文件
Mapper 映射文件 保存 SQL、入参映射、结果映射等信息
SqlSessionFactory 会话工厂,全局通常一个,用来创建 SqlSession
SqlSession 项目和数据库的一次会话,提供执行 SQL 的方法
Executor 执行器,真正调度数据库操作,也负责缓存维护
MappedStatement 封装一条 SQL 的映射信息

一条 SQL 是怎么执行的

可以按 6 步理解:

  1. 读取 MyBatis 全局配置和 Mapper 映射文件。
  2. 构建 SqlSessionFactory
  3. 通过 SqlSessionFactory 创建 SqlSession
  4. 通过 SqlSession 获取 Mapper 接口的代理对象。
  5. 调用 Mapper 方法,底层交给 Executor 执行。
  6. Executor 根据 MappedStatement 完成参数映射、SQL 执行和结果映射。

典型代码如下:

SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(1);
sqlSession.close();

这里看起来是在调用接口方法,实际调用的是 MyBatis 创建的 Mapper 代理对象。代理对象会根据接口方法找到对应的 MappedStatement,再交给执行器处理。

MappedStatement 封装了什么

Mapper XML 里的 SQL:

<select id="selectById" parameterType="int" resultType="User">
    select id, name, age from user where id = #{id}
</select>

最终会被解析成 MappedStatement。它记录了:

  • SQL 语句。
  • 参数类型。
  • 返回值类型。
  • 结果映射规则。
  • 缓存配置。
  • statement 类型。

所以执行器执行 SQL 时,不是只拿一段字符串,而是拿到一份完整的 SQL 执行元数据。

什么是延迟加载

延迟加载就是:需要用到关联数据时再查询,不需要时就不查。

比如用户表和订单表是一对多关系。

查询用户时有两种策略:

加载方式 行为
立即加载 查询用户时,顺便把订单列表也查出来
延迟加载 先只查用户,调用 user.getOrderList() 时再查订单

如果很多场景只需要用户基本信息,不需要订单列表,延迟加载可以减少不必要的 SQL。

MyBatis 支持一对一关联对象、一对多关联集合的延迟加载,但默认不开启,需要配置:

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

延迟加载底层原理

在这里插入图片描述

查询用户
基本信息

创建 User
代理对象

orderList
暂不查询

业务代码调用
getOrderList

进入代理拦截器

关联数据
是否已加载

执行查询
订单 SQL

结果设置到
orderList

直接返回
已有结果

返回订单列表

延迟加载的核心是代理对象。可以按下面流程理解:

  1. MyBatis 查询用户基本信息。
  2. 对用户对象创建代理对象。
  3. 订单列表属性一开始没有真正加载。
  4. 当调用 user.getOrderList() 时,进入代理对象的拦截逻辑。
  5. 拦截器发现订单列表还没加载,于是执行对应 SQL。
  6. 查询订单列表后,调用 setter 把结果设置回对象。
  7. 再继续返回 getOrderList() 的结果。

也就是说,延迟加载并不是字段自己会变出来,而是代理对象拦截了 getter 方法,在第一次访问关联属性时补了一次 SQL 查询。

延迟加载的优缺点

角度 说明
优点 不需要关联数据时少查 SQL,减少一次请求的初始成本
缺点 访问关联属性时会额外发 SQL,容易出现 N+1 查询
适合 关联数据不总是需要、且访问频率可控的场景
不适合 列表页批量访问关联属性,容易一条主查询带出大量子查询

项目里不能一看到延迟加载就觉得高级。列表接口如果循环访问每个用户的订单列表,可能从 1 条 SQL 变成 1 + N 条 SQL。

面试回答模板

可以这样回答:

MyBatis 启动时会读取全局配置文件和 Mapper 映射文件,构建 SqlSessionFactory。执行 SQL 时,通过 SqlSessionFactory 创建 SqlSession,再通过 SqlSession 获取 Mapper 接口代理对象。调用 Mapper 方法时,代理对象会找到对应的 MappedStatement,里面封装了 SQL、参数映射、结果映射等信息,然后交给 Executor 执行器完成参数处理、SQL 执行、结果封装以及缓存维护。

延迟加载可以这样补充:

MyBatis 支持延迟加载,默认关闭。它的底层原理是为目标对象创建代理对象,当调用关联属性的 getter 方法时进入拦截器,如果发现关联数据还没有加载,就执行对应 SQL 查询,再把结果设置到对象属性中。

小结

MyBatis 的主线是:

配置文件 -> SqlSessionFactory -> SqlSession -> Mapper 代理 -> MappedStatement -> Executor -> 数据库 -> 结果映射。

延迟加载是在结果对象这一层做代理增强,真正访问关联属性时再补查 SQL。理解这条链路,MyBatis 的执行流程和延迟加载就能一起讲清楚。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐