1. 什么是 MyBatis?
一句话总结 :MyBatis 是一款轻量级的半自动 ORM(对象关系映射)框架,专注于 SQL 映射,将 Java 对象与数据库表进行关联,简化 JDBC 操作,允许开发者自定义 SQL、存储过程和高级映射。核心要点:
- 前身是 iBatis,2010 年更名 MyBatis;
- 摒弃 JDBC 繁琐的手动参数设置和结果集解析,通过 XML / 注解配置 SQL 与 Java 对象的映射;
- 核心思想:"半自动化"------ 开发者写 SQL,框架处理参数绑定、结果映射、连接管理等。
2. MyBatis 的优缺点?
一句话总结 :MyBatis 的优势是灵活可控 SQL、轻量易集成,缺点是需手动写 SQL、配置工作量大、移植性差。核心要点:
- 优点:
- 高度灵活,可自定义 SQL,适配复杂业务场景(如多表联查、存储过程);
- 轻量级,无第三方依赖,学习成本低;
- 支持动态 SQL、缓存、延迟加载等特性;
- 与 Spring 生态无缝集成。
- 缺点:
- 需手动编写 SQL,开发效率低于全自动 ORM(如 Hibernate);
- SQL 与代码耦合(XML / 注解),维护成本高;
- 数据库移植性差(SQL 语法依赖具体数据库);
- 结果集映射需手动配置,易出错。
3. #{} 和 ${} 的区别是什么?
一句话总结 :#{} 是预编译占位符(安全,防 SQL 注入),${} 是字符串拼接(不安全,适用于动态表名 / 列名)。核心要点:
| 特性 | #{} | ${} |
|---|---|---|
| 底层实现 | PreparedStatement | Statement |
| 处理方式 | 预编译,参数占位符 | 字符串直接拼接 |
| SQL 注入 | 防止 | 无法防止 |
| 适用场景 | 普通参数(值) | 动态表名 / 列名 / 排序 |
| 示例 | WHERE id = #{id} |
ORDER BY ${col} |
4. xml 映射文件中有哪些标签?
一句话总结 :MyBatis XML 映射文件包含核心 SQL 标签、动态 SQL 标签、配置标签,覆盖 SQL 执行、参数处理、结果映射全流程。核心要点:
- 核心 SQL 标签:
<select>(查询)、<insert>(插入)、<update>(更新)、<delete>(删除); - 动态 SQL 标签:
<if>、<where>、<choose>/<when>/<otherwise>、<trim>、<set>、<foreach>; - 配置 / 辅助标签:
<resultMap>(结果映射)、<parameterMap>(参数映射,已过时)、<sql>(可复用 SQL 片段)、<include>(引用 SQL 片段)、<cache>(缓存配置)。
5. 模糊查询 like 语句该怎么写?
一句话总结 :模糊查询推荐用#{}拼接通配符(安全),避免直接用${}(易注入),有两种常用写法。核心要点:
-
写法 1(推荐,Java 层拼接通配符):
java// 代码层 String keyword = "%" + "张三" + "%"; userMapper.selectLike(keyword);XML<!-- XML映射文件 --> <select id="selectLike" resultType="User"> SELECT * FROM user WHERE name LIKE #{keyword} </select> -
写法 2(SQL 层拼接,需注意数据库语法):
XML<select id="selectLike" resultType="User"> <!-- MySQL --> SELECT * FROM user WHERE name LIKE CONCAT('%', #{keyword}, '%') <!-- Oracle --> <!-- SELECT * FROM user WHERE name LIKE '%' || #{keyword} || '%' --> </select> -
注意:禁止写
LIKE '%${keyword}%',会导致 SQL 注入。
6. Mapper 接口的工作原理是什么?Mapper 接口里的方法,参数不同时,方法能重载吗?
一句话总结 :Mapper 接口基于 JDK 动态代理生成实现类,通过接口全类名 + 方法名匹配 XML 中的 SQL;Mapper 方法不能重载 (id 唯一)。核心要点:
- 工作原理:
- MyBatis 启动时,扫描 Mapper 接口并注册到
MapperRegistry; - 调用
getMapper()时,JDK 动态代理生成 Mapper 接口的代理类; - 代理类根据 "接口全类名 + 方法名" 匹配 XML / 注解中的 SQL 节点(id 需与方法名一致);
- 执行 SQL 并处理参数 / 结果映射。
- MyBatis 启动时,扫描 Mapper 接口并注册到
- 方法重载:Mapper 接口方法不能重载 ------XML 中 SQL 的
id是唯一的,重载方法会导致 id 冲突,MyBatis 无法区分。
7. MyBatis 是如何进行分页的?分页插件的原理是什么?
一句话总结 :MyBatis 原生需手动写分页 SQL,分页插件(如 PageHelper)基于拦截器改写 SQL,自动拼接分页语句。核心要点:
-
原生分页(手动实现):
XML<!-- MySQL --> <select id="selectPage" resultType="User"> SELECT * FROM user LIMIT #{offset}, #{size} </select> <!-- Oracle --> <!-- SELECT * FROM (SELECT t.*, ROWNUM rn FROM user t WHERE ROWNUM <= #{end}) WHERE rn >= #{start} --> -
分页插件(PageHelper)原理:
- 基于 MyBatis 的
Interceptor拦截器,拦截Executor的query方法; - 执行 SQL 前,根据方言(MySQL/Oracle)自动拼接
LIMIT/ROWNUM分页语句; - 执行分页 SQL,同时查询总条数(
COUNT(*)); - 将分页结果封装为
Page对象返回。
- 基于 MyBatis 的
8. Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?
一句话总结 :MyBatis 通过反射创建目标对象,根据配置映射列名与属性名,支持自动映射和手动映射两种形式。核心要点:
- 封装流程:
- 执行 SQL 获取
ResultSet; - 根据
resultType/resultMap创建目标对象(反射); - 映射列值到对象属性(匹配列名与属性名);
- 返回单个对象 / 集合。
- 执行 SQL 获取
- 映射形式:
- 自动映射(
resultType):列名与属性名一致时,无需手动配置,直接映射; - 手动映射(
resultMap):列名与属性名不一致、关联查询(一对一 / 一对多)时,自定义映射规则(<id>/<result>/<association>/<collection>)。
- 自动映射(
9. 如何执行批量插入?能返回主键 id 吗
一句话总结 :MyBatis 通过<foreach>标签实现批量插入,支持返回自增主键(MySQL)或序列主键(Oracle)。核心要点:
-
批量插入(MySQL):
XML<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id"> INSERT INTO user (name, age) VALUES <foreach collection="list" item="item" separator=","> (#{item.name}, #{item.age}) </foreach> </insert> -
返回主键:
-
MySQL(自增主键):
<insert>添加useGeneratedKeys="true"+keyProperty="id",插入后实体类的id会被赋值; -
Oracle(序列主键):
XML<insert id="batchInsert"> <selectKey keyProperty="id" order="BEFORE" resultType="Long"> SELECT SEQ_USER.NEXTVAL FROM DUAL </selectKey> INSERT INTO user (id, name, age) VALUES <foreach collection="list" item="item" separator=","> (#{item.id}, #{item.name}, #{item.age}) </foreach> </insert>
-
10. MyBatis 动态 sql 是做什么的?都有哪些动态 sql?能简述一下动态 sql 的执行原理不?
一句话总结 :动态 SQL 用于根据不同条件拼接 SQL 语句,核心标签有<if>/<where>等,底层通过 OGNL 表达式解析条件、拼接 SQL。核心要点:
- 作用:解决静态 SQL 硬编码问题,适配多条件查询、动态更新等场景;
- 核心动态 SQL 标签:
<if>(条件判断)、<where>(自动拼接 WHERE 并去除多余 AND/OR)、<set>(更新时自动拼接 SET 并去除多余逗号)、<foreach>(循环遍历,如批量插入)、<choose>/<when>/<otherwise>(多条件分支); - 执行原理:
- MyBatis 解析 XML 时,将动态 SQL 标签解析为
SqlNode对象; - 执行 SQL 前,通过 OGNL 表达式解析条件参数;
- 根据条件拼接
SqlNode对应的 SQL 片段,生成最终可执行的 SQL。
- MyBatis 解析 XML 时,将动态 SQL 标签解析为
11. MyBatis 能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别。
一句话总结 :MyBatis 支持一对一 / 一对多关联查询,实现方式有嵌套查询(延迟加载)和嵌套结果(一次性加载),前者性能优、后者减少查询次数。核心要点:
表格
| 关联类型 | 实现方式 | 标签 | 特点 |
|---|---|---|---|
| 一对一 | 嵌套结果 | <association> |
单 SQL 联查,一次性加载所有数据,结果映射到关联对象,适用于数据量小的场景 |
| 一对一 | 嵌套查询 | <association> |
多 SQL 查询,先查主表,再根据主键查关联表,支持延迟加载,减少无效查询 |
| 一对多 | 嵌套结果 | <collection> |
单 SQL 联查,一次性加载主表 + 子表数据,结果映射为集合,易产生数据冗余 |
| 一对多 | 嵌套查询 | <collection> |
多 SQL 查询,先查主表,再批量查子表,支持延迟加载,性能更优 |
-
示例(一对多嵌套查询):
XML<resultMap id="UserMap" type="User"> <id column="id" property="id"/> <result column="name" property="name"/> <collection property="orders" select="selectOrdersByUserId" column="id" fetchType="lazy"/> </resultMap>
12. MyBatis 是否支持延迟加载?如果支持,它的实现原理是什么?
一句话总结 :MyBatis 支持延迟加载(懒加载),仅在访问关联对象时才执行查询,底层基于动态代理拦截属性访问。核心要点:
- 开启方式:配置文件中设置
lazyLoadingEnabled=true(全局),或关联查询标签中fetchType="lazy"(局部); - 实现原理:
- 延迟加载时,MyBatis 为关联对象生成动态代理(CGLIB/JDK);
- 首次访问关联对象的属性时,触发代理类的
invoke方法; - 代理类执行预设的关联查询 SQL,加载数据并赋值给关联对象;
- 后续访问直接使用已加载的数据。
- 适用场景:一对多 / 多对多关联查询,减少初始查询的数据量。
13. MyBatis 的 xml 映射文件中,不同的 xml 映射文件的 id 是否可以重复?
一句话总结 :不同 XML 映射文件的id可以重复 ,但需保证 "命名空间(namespace)+ id" 唯一(namespace 对应 Mapper 接口全类名)。核心要点:
- MyBatis 通过
namespace + id唯一标识一个 SQL 节点,而非仅id; - 示例:
UserMapper.xml的id="selectById"和OrderMapper.xml的id="selectById"可共存,因为 namespace 不同; - 注意:同一 XML 映射文件内的
id必须唯一。
14. MyBatis 都有哪些 Executor 执行器?它们之间的区别是什么?
一句话总结 :MyBatis 有 3 种 Executor 执行器(Simple/Reuse/Batch),核心区别在于 Statement 的复用和批量处理能力。核心要点:
表格
| 执行器类型 | 核心特点 | 适用场景 |
|---|---|---|
| Simple | 每次执行 SQL 都创建新的 Statement,执行后关闭,无复用 | 普通查询 / 少量操作 |
| Reuse | 复用 Statement(按 SQL 语句缓存),同一 SQL 重复执行时无需重新创建 | 相同 SQL 多次执行 |
| Batch | 批量执行 SQL,缓存 Statement 并批量提交,减少数据库交互 | 大批量插入 / 更新 / 删除 |
15. MyBatis 中如何指定使用哪一种 Executor 执行器?
一句话总结 :通过配置文件 / 代码指定 Executor 类型,默认 Simple,可全局配置或局部指定。核心要点:
-
全局配置(mybatis-config.xml):
XML<settings> <!-- SIMPLE/REUSE/BATCH --> <setting name="defaultExecutorType" value="BATCH"/> </settings> -
局部指定(代码层,针对单个 SqlSession):
java// 获取批量执行器的SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 执行批量操作 mapper.batchInsert(list); sqlSession.commit();
16. MyBatis 是否可以映射 Enum 枚举类?
一句话总结 :MyBatis 支持映射 Enum 枚举类,可映射枚举名称(默认)或自定义值(需类型处理器)。核心要点:
-
方式 1(默认,映射枚举名称):
java// 枚举类 public enum Sex { MALE, FEMALE } // 实体类 public class User { private Sex sex; } // XML映射(直接用枚举名称) <insert id="insertUser"> INSERT INTO user (sex) VALUES (#{sex}) </insert> -
方式 2(自定义值,需类型处理器):
java// 枚举类(含自定义值) public enum Sex { MALE(1), FEMALE(2); private int code; // 构造器+getter } // 自定义类型处理器 public class SexTypeHandler extends BaseTypeHandler<Sex> { @Override public void setNonNullParameter(PreparedStatement ps, int i, Sex sex, JdbcType jdbcType) throws SQLException { ps.setInt(i, sex.getCode()); } // 实现其他方法(getNullableResult) }XML<!-- 配置类型处理器 --> <resultMap id="UserMap" type="User"> <result column="sex" property="sex" typeHandler="com.example.SexTypeHandler"/> </resultMap>
17. 简述 MyBatis 的 xml 映射文件和 MyBatis 内部数据结构之间的映射关系?
一句话总结 :MyBatis 解析 XML 映射文件后,将标签解析为对应的 Java 对象(MappedStatement/SqlSource 等),存储在 Configuration 中。核心要点:
- 解析流程:
- MyBatis 启动时,解析 XML 映射文件的每个 SQL 标签(
<select>/<insert>等); - 每个 SQL 标签解析为
MappedStatement对象(核心,包含 SQL、参数、结果映射等); MappedStatement中的 SQL(含动态标签)解析为SqlSource对象,动态 SQL 进一步解析为SqlNode;- 结果映射(
<resultMap>)解析为ResultMap对象; - 所有解析后的对象存储在
Configuration(全局配置)中,供执行 SQL 时调用。
- MyBatis 启动时,解析 XML 映射文件的每个 SQL 标签(
18. 为什么说 MyBatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?
一句话总结 :MyBatis 需手动编写 SQL(半自动),全自动 ORM(如 Hibernate)无需写 SQL,仅通过注解 / 配置映射对象与表。核心要点:
| 特性 | MyBatis(半自动 ORM) | Hibernate(全自动 ORM) |
|---|---|---|
| SQL 编写 | 手动编写,灵活可控 | 自动生成,无需手动写 |
| 映射方式 | 需配置 SQL 与对象映射 | 仅需配置对象与表映射 |
| 数据库移植性 | 差(SQL 依赖数据库) | 好(自动适配方言) |
| 学习成本 | 低(专注 SQL) | 高(需掌握 HQL / 缓存等) |
| 适用场景 | 复杂 SQL、性能优化 | 简单 CRUD、快速开发 |
19. Mybatis 的一级、二级缓存?
一句话总结 :一级缓存是 SqlSession 级别的本地缓存(默认开启),二级缓存是 Mapper 级别的全局缓存(需手动开启),均用于减少数据库查询。核心要点:
| 缓存级别 | 作用域 | 开启方式 | 失效场景 |
|---|---|---|---|
| 一级缓存 | SqlSession | 默认开启,无需配置 | 1. SqlSession 关闭 / 提交 / 回滚;2. 执行更新操作;3. 手动清除 |
| 二级缓存 | Mapper(namespace) | 1. 全局配置cacheEnabled=true;2. XML 中加<cache> |
1. 同一 namespace 执行更新操作;2. 缓存过期;3. 手动清除 |
- 执行顺序:查询时先查二级缓存→一级缓存→数据库;更新时先清空对应缓存→执行 SQL。
20. 简述 Mybatis 的插件运行原理,以及如何编写一个插件
一句话总结 :MyBatis 插件基于 JDK 动态代理和拦截器接口,拦截四大核心组件(Executor/StatementHandler 等)的方法,编写插件需实现 Interceptor 接口并配置拦截规则。核心要点:
-
运行原理:
- MyBatis 插件本质是
Interceptor,可拦截四大组件:Executor(执行器)、StatementHandler(SQL 执行)、ParameterHandler(参数处理)、ResultSetHandler(结果处理); - 启动时,插件注册到
InterceptorChain; - 创建核心组件时,通过 JDK 动态代理生成代理类,将插件逻辑织入目标方法;
- 执行目标方法时,触发代理类的
invoke方法,执行插件的拦截逻辑。
- MyBatis 插件本质是
-
编写插件步骤:
- 实现
Interceptor接口,重写intercept(核心拦截逻辑)、plugin(生成代理对象)、setProperties(配置参数); - 用
@Intercepts注解指定拦截的组件和方法; - 注册插件到 MyBatis 配置文件。
- 实现
-
示例(拦截 Executor 的 query 方法):
java@Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class MyPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 前置逻辑:如打印SQL执行时间 long start = System.currentTimeMillis(); Object result = invocation.proceed(); // 执行原方法 // 后置逻辑 long end = System.currentTimeMillis(); System.out.println("SQL执行时间:" + (end - start) + "ms"); return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); // 生成代理对象 } @Override public void setProperties(Properties properties) { // 读取插件配置参数 } }XML<!-- 注册插件 --> <plugins> <plugin interceptor="com.example.MyPlugin"> <property name="param1" value="value1"/> </plugin> </plugins>
整体核心要点回顾
- MyBatis 核心是 "半自动 ORM",灵活可控 SQL,通过 XML / 注解实现 SQL 与 Java 对象映射,支持动态 SQL、缓存、延迟加载等特性;
- 关键特性:
#{}防注入、<foreach>批量操作、resultMap复杂映射、Executor 执行器适配不同 SQL 执行场景; - 扩展能力:分页插件基于拦截器改写 SQL,自定义插件拦截核心组件实现功能增强,二级缓存提升查询性能。