以下是 2025年MyBatis高频面试题及详细解答,覆盖核心原理、实战应用、性能优化、高级特性等面试重点,适合中高级Java工程师求职备考:
一、基础概念类
1. 什么是MyBatis?它的核心优势是什么?
解答:
MyBatis是一款基于Java的持久层框架,前身是iBatis,专注于SQL映射(将Java对象与SQL语句关联),简化JDBC开发流程(无需手动处理Connection、Statement、ResultSet等对象的创建/关闭)。
核心优势:
- 半自动化ORM:SQL语句需手动编写,灵活可控(适合复杂SQL场景);
- 低侵入性:无需实体类继承特定父类或实现接口;
- 易于整合:无缝集成Spring、Spring Boot等主流框架;
- 缓存机制:内置一级缓存(SqlSession级别)和二级缓存(Mapper级别),提升查询效率;
- 动态SQL:通过标签(如
<if> <where>)灵活拼接SQL,适配多条件查询。
2. MyBatis与Hibernate的区别?适用场景是什么?
解答:
| 对比维度 | MyBatis | Hibernate |
|---|---|---|
| ORM类型 | 半自动化(SQL手动编写) | 全自动化(SQL自动生成) |
| SQL控制度 | 高(灵活优化复杂SQL) | 低(难优化复杂SQL) |
| 学习成本 | 低(专注SQL映射) | 高(需掌握HQL、缓存等) |
| 适用场景 | 复杂SQL、性能要求高、互联网项目 | 简单CRUD、快速开发、企业级应用 |
二、核心原理类
3. MyBatis的核心组件有哪些?各自作用是什么?
解答:
MyBatis核心组件通过“职责链模式”协同工作,核心包括:
- SqlSessionFactory:工厂类,由
SqlSessionFactoryBuilder读取配置文件(mybatis-config.xml)创建,用于生成SqlSession; - SqlSession:会话对象,代表与数据库的一次连接,提供CRUD操作的API(如
selectOne() insert()),线程不安全,需用完关闭; - Mapper接口:自定义DAO接口,MyBatis通过动态代理生成实现类,无需手动编写Impl;
- Mapper.xml:SQL映射文件,存储SQL语句、参数映射、结果集映射(
<select> <insert> <resultMap>等标签); - Executor:执行器(MyBatis核心执行引擎),负责SQL解析、参数处理、结果映射,分为
SimpleExecutor(默认,简单执行)、ReuseExecutor(复用Statement)、BatchExecutor(批量执行); - StatementHandler:处理JDBC Statement对象,负责参数设置、SQL执行、结果集封装;
- ResultSetHandler:将JDBC ResultSet映射为Java对象。
4. MyBatis的工作原理(执行流程)是什么?
解答:
- 加载配置:
SqlSessionFactoryBuilder读取mybatis-config.xml(全局配置)和Mapper.xml(SQL映射),解析后生成SqlSessionFactory; - 创建会话:通过
SqlSessionFactory.openSession()创建SqlSession(默认不自动提交事务); - 获取代理:
SqlSession.getMapper(Mapper接口.class)通过JDK动态代理生成Mapper接口的代理对象; - 执行SQL:调用Mapper接口方法,代理对象触发
Executor执行,Executor通过StatementHandler处理JDBC操作:
- 解析SQL(处理动态SQL、参数占位符);
- 绑定参数(将Java对象参数设置到PreparedStatement);
- 执行SQL,获取ResultSet;
-
ResultSetHandler将ResultSet映射为Java对象;
- 关闭会话:执行完成后,关闭
SqlSession(释放数据库连接)。
三、实战应用类
5. MyBatis中#{}和${}的区别?为什么推荐用#{}?
解答:
| 对比维度 | #{} | ${} |
|---|---|---|
| 本质 | 预编译参数占位符(?) | 字符串拼接 |
| 安全性 | 防SQL注入(参数自动转义) | 存在SQL注入风险(直接拼接) |
| 适用场景 | 传递参数(如查询条件、新增字段值) | 动态表名、动态列名(如ORDER BY ${column}) |
示例:
-
select * from user where id = #{id} → 编译为select * from user where id = ?(参数通过setInt()设置,安全); -
select * from user where id = ${id} → 若id传入1 or 1=1,则拼接为select * from user where id = 1 or 1=1(注入风险)。
结论:优先用#{},仅动态表名/列名时用${}(需手动过滤参数,避免注入)。
6. MyBatis如何实现多参数传递?
解答:MyBatis支持4种多参数传递方式,核心是“让MyBatis识别参数名”:
- @Param注解(推荐) :在Mapper接口方法参数上添加
@Param("参数名"),Mapper.xml中直接通过#{参数名}引用:
User selectByUsernameAndAge(@Param("username") String username, @Param("age") int age);
<select id="selectByUsernameAndAge" resultType="User">
select * from user where username = #{username} and age = #{age}
</select>
- Map传递:参数封装为Map,Mapper.xml通过
#{map的key}引用(不推荐,可读性差); - 实体类传递:参数为自定义实体类,Mapper.xml通过
#{实体类属性名}引用(适合参数较多场景); - 默认参数名(MyBatis 3.4+) :无需注解,直接用
#{arg0} #{arg1}或#{param1} #{param2}引用(arg从0开始,param从1开始)。
7. 什么是ResultMap?为什么要用它?
解答:
ResultMap是MyBatis中结果集映射的核心标签,用于定义Java对象与数据库表字段的映射关系(解决“字段名与属性名不一致”问题)。
使用场景:
- 数据库字段名与实体类属性名不同(如数据库
user_name vs 实体userName); - 复杂查询(多表关联、嵌套查询、集合属性映射);
- 避免SQL中写
AS别名(简化SQL)。
示例:
<resultMap id="UserResultMap" type="User">
<id column="user_id" property="userId"/> <!-- 主键映射 -->
<result column="user_name" property="userName"/> <!-- 普通字段映射 -->
<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/> <!-- 类型指定 -->
</resultMap>
<select id="selectById" resultMap="UserResultMap">
select user_id, user_name, create_time from user where user_id = #{id}
</select>
四、动态SQL类
8. MyBatis动态SQL的常用标签有哪些?各自作用是什么?
解答:动态SQL是MyBatis的核心特性,通过标签拼接灵活的SQL语句,常用标签:
-
<if>:条件判断(满足条件才拼接SQL片段);
<where>
<if test="username != null and username != ''">
and username like concat('%', #{username}, '%')
</if>
<if test="age != null">
and age = #{age}
</if>
</where>
-
<where>:替代SQL中的WHERE关键字,自动去除开头的AND/OR(避免拼接错误); -
<set>:用于UPDATE语句,自动去除结尾的,(适配动态更新字段);
<update id="updateUser">
update user
<set>
<if test="userName != null">user_name = #{userName},</if>
<if test="age != null">age = #{age},</if>
</set>
where user_id = #{userId}
</update>
-
<foreach>:遍历集合/数组(如批量查询、批量插入);
<select id="selectByIds" resultType="User">
select * from user where user_id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
- (
collection:集合参数名;item:遍历元素别名;open:拼接开头;close:拼接结尾;separator:元素分隔符) -
<choose> <when> <otherwise>:类似Java的switch-case,只执行一个满足条件的片段。
五、缓存机制类
9. MyBatis的一级缓存和二级缓存有什么区别?如何使用二级缓存?
解答:
MyBatis缓存是为了减少数据库查询次数,提升性能,分为两级缓存:
| 对比维度 | 一级缓存(SqlSession级别) | 二级缓存(Mapper级别) |
|---|---|---|
| 作用范围 | 单个SqlSession(会话内有效) | 同一个Mapper接口(跨SqlSession) |
| 存储介质 | 内存(HashMap) | 内存/第三方缓存(如Redis) |
| 默认状态 | 开启(无法关闭) | 关闭(需手动开启) |
| 失效场景 | SqlSession关闭/提交/回滚;执行update/delete/insert操作 | 对应表执行增删改操作;缓存过期;手动清除 |
二级缓存启用步骤:
- 全局配置文件开启二级缓存(默认已开启,可省略):
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- Mapper.xml中添加
<cache>标签(开启当前Mapper的二级缓存):
<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/>
-
eviction:缓存回收策略(LRU:最近最少使用,默认;FIFO:先进先出); -
flushInterval:缓存过期时间(毫秒); -
size:缓存最大条目数; -
readOnly:是否只读(true:返回缓存对象本身,性能高;false:返回拷贝对象,安全)。
- 实体类需实现
Serializable接口(二级缓存可能序列化存储)。
注意:二级缓存适合查询频繁、修改少的场景;多表关联查询不建议用(缓存失效难以控制)。
六、性能优化类
10. MyBatis的性能优化手段有哪些?
解答:
- SQL优化:
- 避免
SELECT *,只查询需要的字段; - 复杂查询用索引(匹配SQL的WHERE、JOIN条件);
- 分页查询(用MyBatis分页插件
PageHelper,避免一次性查询大量数据)。
- 缓存优化:
- 合理使用二级缓存(查询频繁的Mapper开启);
- 多表关联查询禁用二级缓存(或手动控制缓存失效);
- 自定义缓存(如集成Redis,替代默认内存缓存,支持分布式场景)。
- 参数与结果映射优化:
- 用
ResultMap替代resultType(解决字段名不一致,避免AS别名); - 大字段(如TEXT)延迟加载(通过
<result>标签fetchType="lazy")。
- 执行器优化:
- 批量插入/更新用
BatchExecutor(需手动设置SqlSessionFactory.openSession(ExecutorType.BATCH)); - 复用Statement用
ReuseExecutor(适合频繁执行相同SQL的场景)。
- 连接池优化:
- 整合第三方连接池(如Druid、HikariCP,替代MyBatis默认连接池),配置合理的最大连接数、空闲时间。
- 动态SQL优化:
- 避免复杂嵌套的
<if>标签(可读性差,可拆分SQL); - 用
<where> <set>替代手动拼接AND/OR和,。
七、高级特性类
11. MyBatis的延迟加载(懒加载)是什么?如何实现?
解答:
延迟加载是指关联查询时,只查询主表数据,关联表数据在需要时才查询(避免不必要的关联查询,提升性能)。MyBatis默认关闭懒加载,需手动开启。
实现步骤:
- 全局配置文件开启懒加载:
<settings>
<setting name="lazyLoadingEnabled" value="true"/> <!-- 开启全局懒加载 -->
<setting name="aggressiveLazyLoading" value="false"/> <!-- 关闭积极加载(按需加载) -->
</settings>
- Mapper.xml中定义关联查询(如一对一
association、一对多collection),设置fetchType="lazy":
<resultMap id="UserResultMap" type="User">
<id column="user_id" property="userId"/>
<!-- 一对一关联:懒加载订单信息 -->
<association property="order" column="user_id" select="selectOrderByUserId" fetchType="lazy"/>
</resultMap>
<select id="selectOrderByUserId" resultType="Order">
select * from order where user_id = #{userId}
</select>
原理:懒加载通过动态代理实现,当访问关联属性(如user.getOrder())时,代理对象触发关联SQL的查询。
12. MyBatis如何集成Spring Boot?核心配置是什么?
解答:Spring Boot通过mybatis-spring-boot-starter简化MyBatis集成,核心步骤:
- 引入依赖(Maven):
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.2</version> <!-- 适配Spring Boot版本 -->
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
- 配置application.yml(核心配置):
spring:
datasource:
url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml # Mapper.xml文件路径
type-aliases-package: com.example.entity # 实体类别名包(可省略全类名)
configuration:
map-underscore-to-camel-case: true # 开启下划线转驼峰(user_name → userName)
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
- 启动类添加
@MapperScan(扫描Mapper接口):
@SpringBootApplication
@MapperScan("com.example.mapper") // Mapper接口所在包
public class MyBatisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisDemoApplication.class, args);
}
}
- 编写Mapper接口和Mapper.xml(与原生MyBatis一致),直接注入使用:
@Autowired
private UserMapper userMapper;
public User getUserById(Long id) {
return userMapper.selectById(id);
}
八、问题排查类
13. MyBatis中“Invalid bound statement (not found)”异常的原因有哪些?
解答:该异常是MyBatis最常见异常,核心原因是“Mapper接口与Mapper.xml未正确绑定”,排查方向:
- Mapper接口的全类名与Mapper.xml的
namespace不一致; - Mapper接口的方法名与Mapper.xml中
<select> <insert>等标签的id不一致; - Mapper.xml文件路径未配置(Spring Boot中
mybatis.mapper-locations配置错误); - 编译后target目录中没有Mapper.xml文件(Maven项目需在pom.xml中配置资源过滤);
- 方法参数类型/返回值类型与Mapper.xml中
parameterType/resultType不匹配。
14. MyBatis分页查询时,为什么总返回所有数据(分页失效)?
解答:常见原因:
- 未配置分页插件(如PageHelper),或插件配置错误;
- 分页插件使用顺序错误(需在查询方法前调用
PageHelper.startPage(pageNum, pageSize)); - 查询方法返回值为
List,但未用Page对象接收(Page<User> page = (Page<User>) userMapper.selectAll()); - 动态SQL拼接导致分页SQL被覆盖(如
<where>标签未正确闭合)。
总结
MyBatis面试核心围绕“原理+实战+优化”,重点掌握SqlSession工作流程、动态SQL、缓存机制、参数传递、Spring Boot集成等知识点,同时结合实际项目经验,能解释常见异常排查和性能优化场景,即可应对大部分面试场景。