☰
【面朝大厂】关于Mybatis知识点,面试可以问的都在这里了!2万字详解
2026/10/4 20:04:24 网站建设 项目流程

摘要:MyBatis 是国内 Java 后端面试中出现频率极高的 ORM 框架,很多候选人“会用”,却经不起深挖。本文从核心概念、执行流程、配置与映射、动态 SQL、缓存、延迟加载、插件机制一路讲到与 Hibernate 的对比和高频面试题,覆盖大厂面试中 MyBatis 的常见考查角度,帮你把零散的知识点串成一张完整的知识网络。

一、为什么面试官总爱问 MyBatis

MyBatis 之所以成为面试必问项,一方面是因为它在国内企业中的使用率极高,几乎所有 Java 项目都会接触到它;另一方面,MyBatis 介于纯 JDBC 和全自动 ORM 之间,既能考察候选人对 SQL 的理解,又能考察对框架底层机制的掌握。面试官通过几个连环追问,往往就能判断出一个人的数据库基本功和框架源码阅读能力。

和 Spring、Spring Boot 这种“生态型”框架不同,MyBatis 本身非常轻量,核心代码量不大,但设计十分精巧:它不屏蔽 SQL,让开发者可以精细控制 SQL 的执行;同时通过动态代理、插件、缓存等机制提供了良好的扩展能力。因此,考察 MyBatis 能同时覆盖 SQL 功底、Java 反射与代理、设计模式等多个维度。

二、MyBatis 是什么,解决什么问题

MyBatis 是一个基于 Java 的持久层框架,它把 JDBC 的样板代码封装起来,通过 XML 或注解把 Java 对象和 SQL 语句映射起来,让开发者能专注于 SQL 本身,而不用每次手写获取连接、创建 Statement、遍历 ResultSet、关闭资源这些重复劳动。

它的核心价值可以概括为三点:

  • SQL 可控:SQL 语句写在配置文件中,开发者能对复杂查询进行精细调优,适合对性能要求高、SQL 变化多的业务。
  • 映射灵活:通过resultMap支持复杂的对象关系映射,包括一对一、一对多、多对多。
  • 集成简单:与 Spring、Spring Boot 无缝集成,配置简单,学习成本低。

需要特别强调的一点是:MyBatis 并不是一个“全自动 ORM”,它不负责生成 SQL,也不负责对象关系自动维护。它更像一个“半自动”的数据访问工具,正因如此,它在复杂查询场景下反而比全自动框架更灵活。

三、核心组件与执行流程

理解 MyBatis 的执行流程是回答很多面试题的基础。一个最典型的 MyBatis 查询调用链如下:应用层通过 Mapper 接口调用方法,MyBatis 通过动态代理生成 Mapper 代理对象,代理对象把方法调用转交给SqlSession,再由Executor执行 SQL,最终通过StatementHandler、ParameterHandler、ResultSetHandler完成参数绑定、SQL 执行和结果映射。

1. 核心对象职责

组件作用
SqlSessionFactoryBuilder读取配置文件,构建SqlSessionFactory,用完即可丢弃
SqlSessionFactory创建SqlSession的工厂,应用生命周期内全局唯一
SqlSession一次会话,负责执行 SQL、获取 Mapper,非线程安全,用完需关闭
Executor真正执行 SQL 的执行器,负责缓存、事务、批量处理等
StatementHandler创建 Statement,处理参数,执行 SQL
ParameterHandler把 Java 参数转换成 JDBC 参数
ResultSetHandler把 JDBC 结果集映射成 Java 对象
TypeHandler负责 Java 类型与 JDBC 类型之间的转换

2. 完整的执行流程

  1. 加载核心配置文件mybatis-config.xml,构建Configuration对象。
  2. 解析 Mapper 文件或注解,把 SQL 封装成MappedStatement保存到Configuration中。
  3. 通过SqlSessionFactoryBuilder创建SqlSessionFactory。
  4. 由SqlSessionFactory打开一个SqlSession。
  5. 通过动态代理生成 Mapper 接口的实现对象。
  6. 调用 Mapper 方法,定位到对应的MappedStatement。
  7. 由Executor执行 SQL,期间会经过一级缓存、二级缓存、插件拦截。
  8. 由StatementHandler创建 Statement,ParameterHandler设置参数。
  9. 执行 SQL,ResultSetHandler处理返回结果。
  10. 提交事务并关闭SqlSession。

四、配置文件详解

MyBatis 的核心配置文件通常包含属性、设置、类型别名、环境、插件和映射器几部分。面试中常考的是settings里那些影响缓存的配置,以及typeAliases和environments的作用。

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="jdbc.properties"/> <settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <typeAliases> <package name="com.example.domain"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <package name="com.example.mapper"/> </mappers> </configuration>

几个必须记住的settings配置:cacheEnabled控制二级缓存总开关,lazyLoadingEnabled控制延迟加载总开关,mapUnderscoreToCamelCase开启下划线到驼峰的自动映射,这些属性在面试中被问到的概率很高。

五、Mapper 映射与 SQL 编写

Mapper 的映射方式主要有 XML 和注解两种。XML 方式适合复杂 SQL,尤其是动态 SQL 场景;注解方式代码更简洁,但复杂 SQL 的可读性较差。面试时一般以 XML 方式为主进行考查。

<mapper namespace="com.example.mapper.UserMapper"> <resultMap id="BaseResultMap" type="com.example.domain.User"> <id column="id" property="id"/> <result column="user_name" property="userName"/> <result column="age" property="age"/> </resultMap> <select id="selectById" resultMap="BaseResultMap"> SELECT id, user_name, age FROM t_user WHERE id = #{id} </select> <insert id="insert" parameterType="com.example.domain.User" useGeneratedKeys="true" keyProperty="id"> INSERT INTO t_user (user_name, age) VALUES (#{userName}, #{age}) </insert> </mapper>

这里需要留意useGeneratedKeys和keyProperty的配合,用于返回数据库自动生成的主键。另一个高频考点是namespace必须与 Mapper 接口的全限定名一致,SQL 的id必须与接口方法名一致,这是 MyBatis 绑定接口方法的基础。

六、参数处理与结果映射

1. #{} 与 ${} 的核心区别

这是 MyBatis 面试中几乎是必问的一道题。#{}会把参数作为预编译语句的占位符处理,底层使用PreparedStatement,能有效防止 SQL 注入;${}则是字符串直接拼接,存在 SQL 注入风险,一般只用于动态替换表名、列名、排序字段等无法预编译的场景。

-- 使用 #{} 生成的 SQL(安全) SELECT * FROM t_user WHERE id = ? -- 使用 ${} 生成的 SQL(有注入风险) SELECT * FROM t_user WHERE id = 1 OR 1=1

面试时如果能进一步说明:#{}会经过类型处理器进行类型转换,而${}完全不做处理;并且说明为什么order by不能用#{}(因为预编译占位符会被当作字符串值处理,导致排序字段被引号包裹而失效),会给人留下基本功扎实的印象。

2. 多参数传递

当 Mapper 方法有多个参数时,MyBatis 默认会把参数封装成param1、param2或arg0、arg1的形式,这种方式可读性差,推荐使用@Param注解显式命名。

User selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);

使用@Param后,在 SQL 中通过#{name}和#{age}引用即可。如果只有一个参数,可以直接通过#{任意名}引用。

3. resultType 与 resultMap

resultType用于简单映射,要求查询结果的列名与 Java 对象的属性名一致,或者开启下划线转驼峰配置。当字段与属性不一致,或者存在一对一、一对多等嵌套关系时,就需要使用resultMap进行显式映射。

<resultMap id="UserOrderMap" type="com.example.domain.User"> <id column="id" property="id"/> <result column="user_name" property="userName"/> <collection property="orders" ofType="com.example.domain.Order"> <id column="order_id" property="id"/> <result column="order_no" property="orderNo"/> </collection> </resultMap>

其中association用于一对一关系,collection用于一对多关系,这是resultMap中考查最频繁的两个子标签。面试官可能会继续追问 N+1 查询问题,这时就可以引出延迟加载和嵌套查询的优化。

七、动态 SQL

动态 SQL 是 MyBatis 灵活性的集中体现,常用标签包括if、choose/when/otherwise、trim、where、set、foreach。面试重点通常是where标签如何解决and/or前缀问题,以及foreach批量操作时的细节。

1. 条件拼接

<select id="selectByCondition" resultType="User"> SELECT * FROM t_user <where> <if test="name != null and name != ''"> AND user_name LIKE CONCAT('%', #{name}, '%') </if> <if test="age != null"> AND age = #{age} </if> </where> </select>

where标签会自动处理首个AND或OR,避免出现WHERE AND age = ?这样的非法 SQL。与where对应,更新语句中使用set标签可以自动处理末尾多余的逗号。

2. 批量操作

<insert id="batchInsert"> INSERT INTO t_user (user_name, age) VALUES <foreach collection="list" item="user" separator=","> (#{user.userName}, #{user.age}) </foreach> </insert>

foreach标签的常见属性有collection(集合名)、item(循环项)、index(下标)、open、close、separator(分隔符)。如果传的是数组,collection需要写成array;如果是 List,默认是list,建议配合@Param显式命名。

八、一级缓存与二级缓存

缓存是 MyBatis 面试中的重灾区,很多候选人分不清一级缓存和二级缓存的作用范围、生命周期以及失效条件。先说结论:一级缓存是SqlSession级别,默认开启,无法手动关闭;二级缓存是namespace级别,默认关闭,需要手动开启并配置。

1. 一级缓存

一级缓存存储在SqlSession内部,作用范围是同一个会话。在同一个SqlSession中执行两次完全相同的查询时,第二次会直接命中缓存,不再向数据库发送 SQL。一级缓存基于PerpetualCache实现,本质是一个 HashMap。

一级缓存失效的场景必须记住:

  • 同一个SqlSession中,前后两次查询之间执行了增删改操作,缓存会被清空。
  • 手动调用了clearCache()方法。
  • 配置了二级缓存且开启了事务相关提交操作时,一级缓存的内容会被同步或清空。

值得强调的是,在 Spring 整合 MyBatis 的场景下,每次数据库操作默认都开启一个新的SqlSession,因此一级缓存在单个 Service 方法中往往无法跨调用复用,这正好可以引出对二级缓存的讨论。

2. 二级缓存

二级缓存是 Mapper 级别的缓存,多个SqlSession可以共享,作用范围是同一个namespace。开启二级缓存需要在核心配置中设置cacheEnabled=true,并在 Mapper XML 中添加<cache>标签,同时实体类往往需要实现Serializable接口。

<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

<cache>标签中的属性需要掌握:eviction是淘汰策略,可选LRU、FIFO、SOFT、WEAK,默认LRU;flushInterval表示刷新间隔,单位毫秒;size表示缓存对象数量;readOnly为 true 时返回缓存对象引用,性能更好但不允许修改,为 false 时会返回序列化副本,更安全。

二级缓存的生效流程可以简单理解为:先查二级缓存,再查一级缓存,最后查数据库。这里有一个关键前提:只有会话提交后,一级缓存中的数据才会被写入二级缓存。因此在同一事务尚未提交前,二级缓存通常还看不到本次会话更新的数据。

二级缓存失效或不适用的常见情况包括:

  • 执行了增删改操作,对应 namespace 的二级缓存会被清空。
  • flushCache="true"的查询或语句执行后,会清空相关缓存。
  • 使用useCache="false"的查询不会使用二级缓存。
  • 跨 namespace 的多表关联查询容易出现脏数据,不建议滥用二级缓存。

在面试中,能把“一级缓存的作用域、生命周期、失效场景”和“二级缓存的开启条件、写入时机、脏读风险”分开讲清楚,基本就能应对大多数缓存追问。

九、延迟加载与 N+1 问题

延迟加载也叫懒加载,是指只加载当前需要的对象,当真正访问关联对象时才去查询关联数据。MyBatis 中延迟加载主要用于association和collection的嵌套查询,核心目的是避免一次查询把大量不必要的数据全部加载进来。

延迟加载的全局配置主要有两个:

  • lazyLoadingEnabled:延迟加载总开关,设置为true时开启。
  • aggressiveLazyLoading:设置为false时才是真正的按需加载;若为true,任一属性被访问都会加载所有延迟属性。
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>

也可以在具体的association或collection上使用fetchType="lazy|eager"进行局部控制,优先级高于全局配置。

N+1 问题是指:查询主对象时先执行 1 条 SQL,随后对每个主对象又分别执行 1 条 SQL 去查询关联数据,最终执行了 1+N 条 SQL。例如查询 100 个用户及其订单,会先查用户 1 次,再查订单 100 次,造成大量网络和数据库开销。

常见的解决思路有:

  • 使用连接查询配合resultMap的嵌套结果映射,一次查出全部数据。
  • 按需开启延迟加载,避免加载不使用的关联数据。
  • 使用集合批量查询,把 N 次查询合并为一次IN查询。

十、插件机制与拦截器

MyBatis 的插件机制基于 JDK 动态代理,允许在 SQL 执行过程中对核心组件的目标方法进行拦截。可拦截的对象有四大类:Executor、StatementHandler、ParameterHandler和ResultSetHandler。其中Executor适合拦截查询和增删改,StatementHandler适合处理分页等 SQL 改写场景,ParameterHandler适合参数处理,ResultSetHandler适合结果集处理。

开发插件需要实现Interceptor接口,并使用@Intercepts和@Signature指定要拦截的类、方法和参数类型。

@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class MyPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("before: " + invocation.getMethod().getName()); Object result = invocation.proceed(); System.out.println("after: " + invocation.getMethod().getName()); return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可读取插件配置 } }

配置插件时,在核心配置文件中通过<plugins>标签注册:

<plugins> <plugin interceptor="com.example.plugin.MyPlugin"/> </plugins>

常见的分页插件如 PageHelper 就是利用拦截器在 SQL 执行前改写原 SQL,追加分页条件。其典型用法如下:

PageHelper.startPage(1, 10); List<User> users = userMapper.selectAll(); PageInfo<User> pageInfo = new PageInfo<>(users);

十一、MyBatis 与 Hibernate 的对比

MyBatis 和 Hibernate 是 Java 持久层最常见的两个方案,面试中常被用来考察候选人的技术选型能力。大体上,MyBatis 是半自动 ORM,Hibernate 是全自动 ORM。

维度MyBatisHibernate
SQL 控制手写 SQL,优化空间大自动生成 SQL,深度优化较难
映射复杂度需要维护 XML 或注解实体注解即可,配置相对自动
缓存机制一级加二级缓存,理解较简单一级、二级加查询缓存,更复杂
延迟加载按需配置,较直观默认延迟加载,可能需注意序列化
学习曲线较低,贴近 SQL较高,需要理解持久化上下文
适用场景复杂 SQL、报表、性能敏感对象模型复杂、快速 CRUD 开发

选择建议:如果团队 SQL 能力强、痛点在复杂查询和性能优化,可以优先考虑 MyBatis;如果项目对象模型清晰、以标准增删改查为主,并且希望减少 SQL 维护量,可以优先考虑 Hibernate 或 JPA。

十二、高频面试题速览

以下是 MyBatis 面试中的高频题目,建议配合前面的讲解整理出自己的表达方式。

  • MyBatis 和 JDBC 的关系是什么?核心是封装 JDBC 样板代码,并通过映射简化参数与结果处理。
  • #{} 和 ${} 的区别?前者是预编译占位符,安全;后者是字符串拼接,有注入风险。
  • 一级缓存和二级缓存有什么区别?作用范围不同、开启条件不同、失效场景不同。
  • resultType 和 resultMap 怎么选?简单字段一致用 resultType,复杂映射和嵌套关系用 resultMap。
  • MyBatis 的执行流程是怎样的?从 SqlSessionFactory 到 Mapper 代理、Executor、StatementHandler、ResultSetHandler。
  • Mapper 接口没有实现类,为什么能执行 SQL?通过 JDK 动态代理生成代理对象并绑定 MappedStatement。
  • 如何实现分页?手动 limit、PageHelper 等插件改写 SQL,或在内存中分页。
  • 什么是 N+1 问题,怎么解决?嵌套查询导致的多次 SQL,可改用嵌套结果映射、连接查询或延迟加载。

回答这类问题时,建议采用“结论 + 原理 + 场景 + 注意事项”的结构,既能展示理解深度,也能给面试官留下清晰的逻辑印象。

十三、总结

MyBatis 的面试考查核心可以归纳为一条主线:SQL 如何被找到、参数如何被绑定、结果如何被映射、过程如何被缓存和扩展。沿着“配置加载 - Mapper 绑定 - 执行器处理 - 结果映射 - 缓存与插件”这条链路复习,比零散背八股文更高效。

建议重点准备#{}与${}、两级缓存、resultMap嵌套映射、动态 SQL、延迟加载和插件分页这几个高频方向,并结合自己的项目经验说明实际使用中的注意事项。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询