【面朝大厂】关于Mybatis知识点,面试可以问的都在这里了!2万字详解

发布时间:2026/10/4 20:05:01
【面朝大厂】关于Mybatis知识点,面试可以问的都在这里了!2万字详解
摘要MyBatis 是国内 Java 后端面试中出现频率极高的 ORM 框架很多候选人“会用”却经不起深挖。本文从核心概念、执行流程、配置与映射、动态 SQL、缓存、延迟加载、插件机制一路讲到与 Hibernate 的对比和高频面试题覆盖大厂面试中 MyBatis 的常见考查角度帮你把零散的知识点串成一张完整的知识网络。一、为什么面试官总爱问 MyBatisMyBatis 之所以成为面试必问项一方面是因为它在国内企业中的使用率极高几乎所有 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处理参数执行 SQLParameterHandler把 Java 参数转换成 JDBC 参数ResultSetHandler把 JDBC 结果集映射成 Java 对象TypeHandler负责 Java 类型与 JDBC 类型之间的转换2. 完整的执行流程加载核心配置文件mybatis-config.xml构建Configuration对象。解析 Mapper 文件或注解把 SQL 封装成MappedStatement保存到Configuration中。通过SqlSessionFactoryBuilder创建SqlSessionFactory。由SqlSessionFactory打开一个SqlSession。通过动态代理生成 Mapper 接口的实现对象。调用 Mapper 方法定位到对应的MappedStatement。由Executor执行 SQL期间会经过一级缓存、二级缓存、插件拦截。由StatementHandler创建 StatementParameterHandler设置参数。执行 SQLResultSetHandler处理返回结果。提交事务并关闭SqlSession。四、配置文件详解MyBatis 的核心配置文件通常包含属性、设置、类型别名、环境、插件和映射器几部分。面试中常考的是settings里那些影响缓存的配置以及typeAliases和environments的作用。?xml version1.0 encodingUTF-8 ? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN https://mybatis.org/dtd/mybatis-3-config.dtd configuration properties resourcejdbc.properties/ settings setting namecacheEnabled valuetrue/ setting namelazyLoadingEnabled valuetrue/ setting nameaggressiveLazyLoading valuefalse/ setting namemapUnderscoreToCamelCase valuetrue/ /settings typeAliases package namecom.example.domain/ /typeAliases environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /dataSource /environment /environments mappers package namecom.example.mapper/ /mappers /configuration几个必须记住的settings配置cacheEnabled控制二级缓存总开关lazyLoadingEnabled控制延迟加载总开关mapUnderscoreToCamelCase开启下划线到驼峰的自动映射这些属性在面试中被问到的概率很高。五、Mapper 映射与 SQL 编写Mapper 的映射方式主要有 XML 和注解两种。XML 方式适合复杂 SQL尤其是动态 SQL 场景注解方式代码更简洁但复杂 SQL 的可读性较差。面试时一般以 XML 方式为主进行考查。mapper namespacecom.example.mapper.UserMapper resultMap idBaseResultMap typecom.example.domain.User id columnid propertyid/ result columnuser_name propertyuserName/ result columnage propertyage/ /resultMap select idselectById resultMapBaseResultMap SELECT id, user_name, age FROM t_user WHERE id #{id} /select insert idinsert parameterTypecom.example.domain.User useGeneratedKeystrue keyPropertyid 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 11面试时如果能进一步说明#{}会经过类型处理器进行类型转换而${}完全不做处理并且说明为什么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 与 resultMapresultType用于简单映射要求查询结果的列名与 Java 对象的属性名一致或者开启下划线转驼峰配置。当字段与属性不一致或者存在一对一、一对多等嵌套关系时就需要使用resultMap进行显式映射。resultMap idUserOrderMap typecom.example.domain.User id columnid propertyid/ result columnuser_name propertyuserName/ collection propertyorders ofTypecom.example.domain.Order id columnorder_id propertyid/ result columnorder_no propertyorderNo/ /collection /resultMap其中association用于一对一关系collection用于一对多关系这是resultMap中考查最频繁的两个子标签。面试官可能会继续追问 N1 查询问题这时就可以引出延迟加载和嵌套查询的优化。七、动态 SQL动态 SQL 是 MyBatis 灵活性的集中体现常用标签包括if、choose/when/otherwise、trim、where、set、foreach。面试重点通常是where标签如何解决and/or前缀问题以及foreach批量操作时的细节。1. 条件拼接select idselectByCondition resultTypeUser SELECT * FROM t_user where if testname ! null and name ! AND user_name LIKE CONCAT(%, #{name}, %) /if if testage ! null AND age #{age} /if /where /selectwhere标签会自动处理首个AND或OR避免出现WHERE AND age ?这样的非法 SQL。与where对应更新语句中使用set标签可以自动处理末尾多余的逗号。2. 批量操作insert idbatchInsert INSERT INTO t_user (user_name, age) VALUES foreach collectionlist itemuser separator, (#{user.userName}, #{user.age}) /foreach /insertforeach标签的常见属性有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。开启二级缓存需要在核心配置中设置cacheEnabledtrue并在 Mapper XML 中添加cache标签同时实体类往往需要实现Serializable接口。cache evictionLRU flushInterval60000 size512 readOnlytrue/cache标签中的属性需要掌握eviction是淘汰策略可选LRU、FIFO、SOFT、WEAK默认LRUflushInterval表示刷新间隔单位毫秒size表示缓存对象数量readOnly为 true 时返回缓存对象引用性能更好但不允许修改为 false 时会返回序列化副本更安全。二级缓存的生效流程可以简单理解为先查二级缓存再查一级缓存最后查数据库。这里有一个关键前提只有会话提交后一级缓存中的数据才会被写入二级缓存。因此在同一事务尚未提交前二级缓存通常还看不到本次会话更新的数据。二级缓存失效或不适用的常见情况包括执行了增删改操作对应 namespace 的二级缓存会被清空。flushCachetrue的查询或语句执行后会清空相关缓存。使用useCachefalse的查询不会使用二级缓存。跨 namespace 的多表关联查询容易出现脏数据不建议滥用二级缓存。在面试中能把“一级缓存的作用域、生命周期、失效场景”和“二级缓存的开启条件、写入时机、脏读风险”分开讲清楚基本就能应对大多数缓存追问。九、延迟加载与 N1 问题延迟加载也叫懒加载是指只加载当前需要的对象当真正访问关联对象时才去查询关联数据。MyBatis 中延迟加载主要用于association和collection的嵌套查询核心目的是避免一次查询把大量不必要的数据全部加载进来。延迟加载的全局配置主要有两个lazyLoadingEnabled延迟加载总开关设置为true时开启。aggressiveLazyLoading设置为false时才是真正的按需加载若为true任一属性被访问都会加载所有延迟属性。settings setting namelazyLoadingEnabled valuetrue/ setting nameaggressiveLazyLoading valuefalse/ /settings也可以在具体的association或collection上使用fetchTypelazy|eager进行局部控制优先级高于全局配置。N1 问题是指查询主对象时先执行 1 条 SQL随后对每个主对象又分别执行 1 条 SQL 去查询关联数据最终执行了 1N 条 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 interceptorcom.example.plugin.MyPlugin/ /plugins常见的分页插件如 PageHelper 就是利用拦截器在 SQL 执行前改写原 SQL追加分页条件。其典型用法如下PageHelper.startPage(1, 10); ListUser users userMapper.selectAll(); PageInfoUser pageInfo new PageInfo(users);十一、MyBatis 与 Hibernate 的对比MyBatis 和 Hibernate 是 Java 持久层最常见的两个方案面试中常被用来考察候选人的技术选型能力。大体上MyBatis 是半自动 ORMHibernate 是全自动 ORM。维度MyBatisHibernateSQL 控制手写 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或在内存中分页。什么是 N1 问题怎么解决嵌套查询导致的多次 SQL可改用嵌套结果映射、连接查询或延迟加载。回答这类问题时建议采用“结论 原理 场景 注意事项”的结构既能展示理解深度也能给面试官留下清晰的逻辑印象。十三、总结MyBatis 的面试考查核心可以归纳为一条主线SQL 如何被找到、参数如何被绑定、结果如何被映射、过程如何被缓存和扩展。沿着“配置加载 - Mapper 绑定 - 执行器处理 - 结果映射 - 缓存与插件”这条链路复习比零散背八股文更高效。建议重点准备#{}与${}、两级缓存、resultMap嵌套映射、动态 SQL、延迟加载和插件分页这几个高频方向并结合自己的项目经验说明实际使用中的注意事项。