Spring + MyBatis 高频面试题(含口述版回答)
1. 什么是 MyBatis?和 Hibernate 有什么区别?
口述版:
MyBatis 是一个半自动的 ORM 框架,重点在于你自己写 SQL,它负责把结果映射成 Java 对象。
和 Hibernate 的主要区别:
| 对比点 | MyBatis | Hibernate |
|---|---|---|
| SQL 控制 | 自己写 SQL,灵活 | 自动生成,复杂 SQL 难优化 |
| 学习成本 | 较低 | 较高 |
| 性能优化 | 直接优化 SQL,直观 | 需要理解缓存、抓取策略等 |
| 映射方式 | 结果映射为主 | 全对象关系映射 |
| 适用场景 | SQL 复杂、需要精细控制 | 业务标准、快速开发 |
国内互联网公司更常用 MyBatis(或 MyBatis-Plus),因为 SQL 可控性更强,优化起来直观。
2. #{} 和 ${} 有什么区别?
口述版:
这是必考题。
#{}:预编译,参数会以占位符?的形式传入,MyBatis 会做预处理,有效防止 SQL 注入。优先使用。${}:直接字符串拼接,没有预编译,存在 SQL 注入风险。一般只用于传入表名、列名等动态字段,且这些值不能来自用户输入。
简单记:能用 #{} 就用 #{},只有在必须动态拼接 SQL 结构时才用 ${},并做好白名单校验。
3. MyBatis 的一级缓存和二级缓存是什么?
口述版:
一级缓存(Local Cache):
- 默认开启,作用域是 SqlSession。
- 同一个 SqlSession 里,相同的查询会走缓存。
- SqlSession 关闭或清空后缓存就没了。
- Spring 集成后,通常每次请求一个 SqlSession,所以一级缓存作用有限。
二级缓存(Second Level Cache):
- 作用域是 Mapper 级别(同一个 namespace)。
- 需要手动开启(全局配置 + Mapper 上配置)。
- 可以跨 SqlSession 共享。
- 默认是不开启的,实际项目中用得相对少,更多用 Redis 做业务缓存。
注意:二级缓存有脏读风险,尤其是在多表关联更新时,使用要谨慎。
4. MyBatis 执行一次查询的完整流程是什么?
口述版:
大致流程:
- 读取配置文件 / 注解,创建
SqlSessionFactory。 - 通过
SqlSessionFactory打开一个SqlSession。 - 从 SqlSession 中获取 Mapper 接口的代理对象。
- 调用 Mapper 方法,代理对象根据方法签名找到对应的
MappedStatement。 - 通过 Executor 执行(根据配置可能是 Simple、Reuse、Batch)。
- 参数映射(ParameterHandler)把 Java 对象转成 JDBC 参数。
- 执行 SQL,得到 ResultSet。
- 结果映射(ResultSetHandler)把结果转成 Java 对象。
- 返回结果,关闭资源。
核心组件:SqlSessionFactory、SqlSession、Executor、MappedStatement、ParameterHandler、ResultSetHandler。
5. Mapper 接口是怎么工作的?为什么没有实现类也能运行?
口述版:
Mapper 接口是通过 JDK 动态代理 实现的。
启动时 MyBatis 会为每个 Mapper 接口创建一个代理对象。调用接口方法时,代理对象会:
- 根据方法名和参数找到对应的 XML 或注解里的 SQL
- 把调用转发给 SqlSession 去执行真正的数据库操作
所以我们只写接口,不需要写实现类,运行时 MyBatis 自动生成代理完成工作。这也是为什么方法签名必须和 XML 里的 id 对应上。
6. resultType 和 resultMap 有什么区别?什么时候用 resultMap?
口述版:
resultType:直接指定返回结果的类型(实体类全限定名或别名)。字段名和属性名能对应上(或开启驼峰转换)时使用,简单方便。resultMap:自定义结果映射规则,可以处理:- 字段名和属性名不一致
- 一对一、一对多关联查询
- 复杂映射、鉴别器等
使用建议:
- 单表简单查询优先用
resultType - 多表关联、字段映射复杂时用
resultMap
7. MyBatis 如何获取自增主键?
口述版:
常用两种方式:
1. 使用 useGeneratedKeys(推荐,适合 MySQL):
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into user(name, age) values(#{name}, #{age})
</insert>
插入后主键会自动回填到对象的 id 属性上。
2. 使用 <selectKey>(兼容性更好,适合 Oracle 等):
<insert id="insertUser">
<selectKey keyProperty="id" resultType="long" order="BEFORE">
SELECT SEQ_USER.NEXTVAL FROM DUAL
</selectKey>
insert into user(id, name) values(#{id}, #{name})
</insert>
8. 动态 SQL 有哪些常用标签?
口述版:
MyBatis 动态 SQL 是它的强项,常用标签:
<if>:条件判断<where>:自动处理 WHERE 和多余的 AND/OR<set>:用于更新语句,自动去掉末尾逗号<choose> <when> <otherwise>:类似 switch-case<foreach>:遍历集合,常用于 IN 查询和批量插入<trim>:更灵活的前缀后缀处理<sql>+<include>:SQL 片段复用
其中 <foreach> 和 <where> 使用频率最高。
9. Spring 是怎么整合 MyBatis 的?
口述版:
主流整合方式(以 Spring Boot 为例):
- 引入
mybatis-spring-boot-starter依赖。 - 配置数据源(application.yml)。
- 配置 MyBatis 相关属性(mapper 位置、类型别名、驼峰等)。
- 在启动类或配置类上加
@MapperScan("com.xxx.mapper"),扫描 Mapper 接口。 - 或者在每个 Mapper 接口上加
@Mapper。
底层关键是:
- Spring 管理
SqlSessionFactory和数据源 - 通过
MapperFactoryBean或扫描方式,把 Mapper 接口注册成 Spring Bean - 利用 Spring 的事务管理器(
@Transactional)统一管理事务
10. @Mapper 和 @MapperScan 有什么区别?
口述版:
@Mapper:加在单个 Mapper 接口上,声明这是一个 Mapper,Spring 会为它创建代理 Bean。@MapperScan:加在配置类或启动类上,批量扫描指定包下的所有 Mapper 接口,一次性注册。
项目中 Mapper 多的时候,用 @MapperScan 更方便,不用每个接口都加 @Mapper。两者可以同时存在。
11. MyBatis 的懒加载是什么?怎么开启?
口述版:
懒加载(延迟加载)主要用于关联查询:先查主表,等真正访问关联对象时才去查从表。
开启方式:
- 全局配置
lazyLoadingEnabled=true - 在关联映射上设置
fetchType="lazy"(association/collection)
注意:
- 懒加载依赖动态代理,序列化时可能出问题
- 容易引发 N+1 查询问题
- 实际项目中很多团队会关掉懒加载,用手动关联或业务层控制
12. MyBatis 插件(Interceptor)原理是什么?
口述版:
MyBatis 插件采用责任链 + JDK 动态代理实现。
可以拦截的四大对象:
- Executor
- ParameterHandler
- ResultSetHandler
- StatementHandler
实现步骤:
- 实现
Interceptor接口 - 使用
@Intercepts+@Signature标注要拦截的方法 - 在
intercept方法里做逻辑(可调用invocation.proceed()放行) - 在配置文件或 Spring 中注册插件
常见用途:分页插件(PageHelper)、SQL 性能监控、数据权限过滤、审计日志等。
13. 批量插入/更新怎么做比较高效?
口述版:
几种常见方式:
-
<foreach>拼接多值插入(适合数据量不是特别大):insert into user(name, age) values <foreach collection="list" item="item" separator=","> (#{item.name}, #{item.age}) </foreach> -
ExecutorType.BATCH: 通过
SqlSessionFactory.openSession(ExecutorType.BATCH)获取 Batch 模式的 SqlSession,多次执行后统一 flush。在 Spring 里可以用SqlSessionTemplate或@Transactional+ 特殊配置。 -
MyBatis-Plus 的
saveBatch:底层也是基于批量执行,使用方便。
注意控制每批数量(比如 500~1000 条),避免 SQL 过长或事务过大。
14. MyBatis 中如何处理一对一、一对多关联查询?
口述版:
主要用 resultMap 配合:
- 一对一:
<association>标签(或嵌套 select / 嵌套结果) - 一对多:
<collection>标签
两种实现方式:
- 嵌套结果(nested result):一次 SQL 查出所有数据,MyBatis 负责组装(注意重复数据问题)。
- 嵌套查询(nested select):先查主表,再根据关联字段查从表(容易 N+1,可配合懒加载)。
实际项目中更推荐业务层分次查询或自己写好关联 SQL,用嵌套结果映射,可控性更强。
15. Spring 事务和 MyBatis 是怎么配合的?
口述版:
Spring 通过 @Transactional 管理事务,底层用的是 PlatformTransactionManager(通常是 DataSourceTransactionManager)。
整合 MyBatis 后:
- MyBatis 的 SqlSession 会参与到 Spring 的事务中
- 同一个事务里多次操作共用同一个 Connection
- 事务提交或回滚由 Spring 统一控制
注意事项:
@Transactional默认只对 RuntimeException 回滚,受检异常需要指定rollbackFor- 方法必须是 public,且从外部调用才会生效(自调用失效)
- 多数据源时要明确指定事务管理器
面试加分点
- 能说清
#{}与${}的区别和安全问题。 - 理解 Mapper 接口的动态代理原理。
- 知道一级缓存、二级缓存的作用域和实际使用建议。
- 能讲清楚 Spring 整合 MyBatis 的核心步骤。
- 实际用过 PageHelper、批量操作、动态 SQL,能结合项目讲。
- 了解 MyBatis-Plus 的常用功能(条件构造器、自动填充、逻辑删除等)是加分项。
快速记忆框架:
Spring + MyBatis 面试核心:
基础概念 → #{} vs ${} → 缓存 → 执行流程与动态代理 → 结果映射 → 动态 SQL → 主键回填 → Spring 整合 → 事务 → 插件与批量
把「动态代理」和「缓存」讲清楚,再结合自己项目里写过的复杂 SQL 和批量操作,基本就能应对大部分面试。