MyBatis 高频八股文:从 Mapper 到缓存,一篇搞懂常见面试题
前言
MyBatis 是 Java 后端开发中非常常用的持久层框架,主要负责 Java 程序和数据库之间的数据交互。
在 Spring Boot 项目中,我们经常会用 MyBatis 或 MyBatis-Plus 来操作 MySQL。
面试中,MyBatis 常见问题包括:
- MyBatis 是什么?
- #{} 和 ${} 有什么区别?
- MyBatis 如何防止 SQL 注入?
- MyBatis 一级缓存和二级缓存是什么?
- Mapper 接口为什么不需要实现类?
- MyBatis 动态 SQL 怎么写?
- MyBatis 和 Hibernate 有什么区别?
- MyBatis 分页怎么实现?
这篇文章整理一份 MyBatis 高频八股文,适合初学者和准备面试的同学快速复习。
1. MyBatis 是什么?
MyBatis 是一个优秀的持久层框架。
它主要用于简化 Java 操作数据库的过程。
传统 JDBC 操作数据库时,需要手动:
获取连接 创建 Statement 执行 SQL 处理结果集 关闭资源
代码比较繁琐。
MyBatis 帮我们封装了这些重复操作,开发者只需要关注:
SQL 怎么写 参数怎么传 结果怎么映射
简单来说:
MyBatis 是一个半自动 ORM 框架,用来简化数据库操作。
2. 什么是 ORM?
ORM 全称是 Object Relational Mapping,中文叫对象关系映射。
它的作用是:
把 Java 对象和数据库表建立映射关系。
比如数据库中有一张用户表:
user
Java 中有一个实体类:
public class User { private Long id; private String username; private Integer age; }ORM 框架可以帮助我们把数据库中的一行记录映射成 Java 对象。
MyBatis 属于半自动 ORM,因为 SQL 通常需要开发者自己写。
3. MyBatis 的优点有哪些?
MyBatis 常见优点:
- SQL 写在 XML 或注解中,灵活性高。
- 封装了 JDBC,减少重复代码。
- 支持动态 SQL。
- 支持结果映射。
- 学习成本相对较低。
- 方便进行 SQL 优化。
- 和 Spring Boot 整合方便。
相比全自动 ORM,MyBatis 更适合复杂 SQL 场景。
4. MyBatis 和 JDBC 有什么区别?
| 对比项 | JDBC | MyBatis |
|---|---|---|
| SQL 编写 | 写在 Java 代码中 | 写在 XML 或注解中 |
| 参数处理 | 手动设置 | 自动处理 |
| 结果集处理 | 手动封装 | 自动映射 |
| 资源关闭 | 手动关闭 | 框架管理 |
| 开发效率 | 较低 | 较高 |
JDBC 更底层,MyBatis 是对 JDBC 的封装。
5. MyBatis 核心组件有哪些?
MyBatis 核心组件主要有:
SqlSessionFactoryBuilder SqlSessionFactory SqlSession Executor MappedStatement Mapper简单理解:
- SqlSessionFactoryBuilder:用于构建 SqlSessionFactory。
- SqlSessionFactory:用于创建 SqlSession。
- SqlSession:执行 SQL 的核心对象。
- Executor:真正执行 SQL 的执行器。
- MappedStatement:封装 SQL 信息。
- Mapper:我们写的数据库操作接口。
实际开发中,Spring Boot 会帮我们管理很多对象。
6. MyBatis 执行流程是什么?
MyBatis 执行 SQL 的大致流程:
- 读取 MyBatis 配置文件。
- 加载 Mapper XML 文件。
- 创建 SqlSessionFactory。
- 创建 SqlSession。
- 通过 Mapper 代理对象调用方法。
- 找到对应的 SQL 语句。
- 设置 SQL 参数。
- 执行 SQL。
- 封装结果集。
- 返回 Java 对象。
简单总结:
Mapper 方法调用后,MyBatis 会根据方法找到对应 SQL,然后执行并把结果映射成对象。
7. Mapper 接口为什么不需要实现类?
因为 MyBatis 会为 Mapper 接口创建动态代理对象。
我们只需要定义接口:
public interface UserMapper { User selectById(Long id); }然后在 XML 中写对应 SQL:
<select id="selectById" resultType="User"> select * from user where id = #{id} </select>MyBatis 会根据接口方法名和 XML 中的 SQL id 进行绑定。
调用 Mapper 方法时,本质上调用的是 MyBatis 生成的代理对象。
8. #{} 和 ${} 有什么区别?
这是 MyBatis 高频面试题。
#{}
#{} 是预编译占位符。
底层使用的是 PreparedStatement。
例如:
select * from user where id = #{id}
会被处理成:
select * from user where id = ?
可以防止 SQL 注入。
${}
${} 是字符串拼接。
例如:
select * from user order by ${column}
它会直接把参数拼接到 SQL 中。
如果参数不可信,容易产生 SQL 注入风险。
简单总结:
#{} 是预编译,安全;${} 是字符串拼接,不安全。
9. MyBatis 如何防止 SQL 注入?
MyBatis 推荐使用:
#{}
因为 #{} 会使用预编译参数,用户输入不会直接拼接到 SQL 中。
例如:
<select id="selectByName" resultType="User"> select * from user where username = #{username} </select>这样可以避免用户输入恶意 SQL。
但是如果使用 ${}:
select * from user where username = '${username}'就可能存在 SQL 注入风险。
所以实际开发中:
能用 #{} 就不要用 ${}。
10. ${} 一般用在什么场景?
${} 虽然有风险,但有些场景必须使用。
比如动态表名、动态字段名、排序字段:
select * from ${tableName}
order by ${column}
因为表名和字段名不能用预编译参数替代。
但是使用 ${} 时,一定要做白名单校验。
例如排序字段只能允许:
id create_time username
不能直接相信前端传来的参数。
11. resultType 和 resultMap 有什么区别?
resultType
resultType 用于简单映射。
当数据库字段名和 Java 属性名能对应上时,可以直接使用。
<select id="selectById" resultType="User"> select id, username, age from user where id = #{id} </select>resultMap
resultMap 用于复杂映射。
比如字段名和属性名不一致,或者一对一、一对多查询。
<resultMap id="userMap" type="User"> <id property="id" column="id"/> <result property="userName" column="user_name"/> </resultMap>简单总结:
简单映射用 resultType,复杂映射用 resultMap。
12. 数据库字段名和 Java 属性名不一致怎么办?
比如数据库字段是:
user_name
Java 属性是:
private String userName;
解决方式有几种:
使用别名
select user_name as userName from user使用 resultMap
<result property="userName" column="user_name"/>开启驼峰映射
mybatis: configuration: map-underscore-to-camel-case: true开启后,user_name 可以自动映射成 userName。
13. MyBatis 动态 SQL 是什么?
动态 SQL 指的是根据不同条件生成不同 SQL。
比如查询用户时,用户名、年龄、状态都可能不是必填。
MyBatis 提供了很多动态 SQL 标签:
if where set trim choose when otherwise foreach动态 SQL 可以减少手动拼接 SQL 的麻烦。
14. if 标签怎么用?
if 用于条件判断。
<select id="selectUser" resultType="User"> select * from user where 1 = 1 <if test="username != null and username != ''"> and username = #{username} </if> <if test="age != null"> and age = #{age} </if> </select>如果 username 不为空,就拼接 username 条件。
如果 age 不为空,就拼接 age 条件。
15. where 标签有什么作用?
where 标签会自动处理 SQL 中的 where 和多余的 and。
<select id="selectUser" resultType="User"> select * from user <where> <if test="username != null and username != ''"> and username = #{username} </if> <if test="age != null"> and age = #{age} </if> </where> </select>如果没有任何条件,where 不会生成。
如果第一个条件前面有 and,它会自动去掉。
16. foreach 标签有什么作用?
foreach 常用于批量操作或 in 查询。
例如根据多个 id 查询用户:
<select id="selectByIds" resultType="User"> select * from user where id in <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> </select>生成的 SQL 类似:
select * from user where id in (1,2,3)
17. MyBatis 如何实现分页?
常见分页方式有两种:
使用 limit
select * from user limit #{offset}, #{pageSize}
使用分页插件
实际项目中常用:
PageHelper MyBatis-Plus 分页插件
例如 PageHelper:
PageHelper.startPage(pageNum, pageSize); List<User> list = userMapper.selectList();分页插件底层通常会拦截 SQL,然后自动拼接分页语句。
18. MyBatis 插件原理是什么?
MyBatis 插件本质上是拦截器。
它可以拦截 MyBatis 中的部分核心对象方法,例如:
Executor StatementHandler ParameterHandler ResultSetHandler常见插件功能:
分页 SQL 日志 性能监控 权限控制 数据脱敏
分页插件就是通过拦截 SQL 执行过程,修改 SQL 来实现分页。
19. MyBatis 一级缓存是什么?
一级缓存是 SqlSession 级别的缓存。
默认开启。
同一个 SqlSession 中,执行相同查询,第二次可以直接从缓存中获取结果,不再查询数据库。
例如:
User user1 = mapper.selectById(1L); User user2 = mapper.selectById(1L);如果在同一个 SqlSession 中,第二次查询可能会走一级缓存。
但是如果执行了增删改操作,一级缓存会被清空。
20. MyBatis 二级缓存是什么?
二级缓存是 Mapper 级别的缓存。
多个 SqlSession 可以共享同一个 Mapper 的二级缓存。
二级缓存默认不开启,需要手动配置。
开启方式:
<cache/>
还需要实体类支持序列化。
不过实际项目中,MyBatis 二级缓存用得不多。
因为它容易带来数据一致性问题。
实际开发中更常用 Redis 作为缓存。
21. 一级缓存和二级缓存有什么区别?
| 对比项 | 一级缓存 | 二级缓存 |
|---|---|---|
| 级别 | SqlSession 级别 | Mapper 级别 |
| 是否默认开启 | 默认开启 | 默认关闭 |
| 作用范围 | 同一个 SqlSession | 多个 SqlSession |
| 实际使用 | 默认存在 | 较少使用 |
简单总结:
一级缓存是 SqlSession 级别,二级缓存是 Mapper 级别。
22. MyBatis 延迟加载是什么?
延迟加载也叫懒加载。
它指的是:
需要用到关联对象时,才去查询数据库。
比如查询用户时,不一定马上查询用户订单。
只有调用:
user.getOrders()
时,才查询订单数据。
延迟加载可以减少不必要的数据库查询。
但是如果使用不当,也可能产生很多额外 SQL。
23. MyBatis 一对一和一对多怎么处理?
MyBatis 可以使用 resultMap 处理关联关系。
一对一
使用:
<association>
例如用户对应一个身份证信息。
一对多
使用:
<collection>
例如一个用户有多个订单。
简单记:
association:一对一 collection:一对多
24. MyBatis 批量插入怎么写?
可以使用 foreach。
<insert id="insertBatch"> insert into user(username, age) values <foreach collection="list" item="user" separator=","> (#{user.username}, #{user.age}) </foreach> </insert>这样可以一次插入多条数据。
相比循环一条条插入,批量插入效率更高。
25. MyBatis 和 Hibernate 有什么区别?
| 对比项 | MyBatis | Hibernate |
|---|---|---|
| 类型 | 半自动 ORM | 全自动 ORM |
| SQL | 需要自己写 | 自动生成较多 |
| 灵活性 | 高 | 相对较低 |
| 学习成本 | 较低 | 较高 |
| SQL 优化 | 方便 | 相对不直观 |
| 适合场景 | 复杂 SQL | 标准 ORM 场景 |
简单回答:
MyBatis 更灵活,适合复杂 SQL;Hibernate 自动化程度更高,但 SQL 可控性较弱。
国内 Java 后端项目中,MyBatis 使用非常广泛。
26. MyBatis 和 MyBatis-Plus 有什么区别?
MyBatis-Plus 是 MyBatis 的增强工具。
它不是替代 MyBatis,而是在 MyBatis 基础上增强。
MyBatis-Plus 提供了很多常用功能:
通用 CRUD 条件构造器 分页插件 代码生成器 逻辑删除 自动填充 乐观锁插件
使用 MyBatis-Plus 后,很多简单 CRUD 不需要再手写 SQL。
例如:
userMapper.selectById(1L); userMapper.insert(user); userMapper.updateById(user);简单总结:
MyBatis-Plus 是 MyBatis 的增强版,可以提高开发效率。
27. MyBatis-Plus 的 Wrapper 是什么?
Wrapper 是 MyBatis-Plus 提供的条件构造器。
它可以用 Java 代码拼接查询条件。
例如:
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User::getStatus, 1) .like(User::getUsername, "zhang"); List<User> list = userMapper.selectList(wrapper);这样可以避免手写简单 SQL。
常见 Wrapper 有:
QueryWrapper LambdaQueryWrapper UpdateWrapper LambdaUpdateWrapper
实际开发中更推荐使用 LambdaQueryWrapper,因为它可以避免字段名写错。
28. MyBatis 常见注解有哪些?
常见注解包括:
@Mapper @Param @Select @Insert @Update @Delete@Mapper
标记 Mapper 接口。
@Param
给参数命名。
User selectByNameAndAge(@Param("name") String name, @Param("age") Integer age);@Select
直接在注解中写查询 SQL。
@Select("select * from user where id = #{id}") User selectById(Long id);简单 SQL 可以用注解,复杂 SQL 更推荐写在 XML 中。
29. @Param 注解有什么作用?
@Param 用于给 Mapper 方法参数命名。
例如:
User selectUser(@Param("username") String username, @Param("age") Integer age);XML 中可以这样使用:
select * from user where username = #{username} and age = #{age}如果方法有多个参数,建议加上 @Param,避免参数名绑定出问题。
30. MyBatis 面试怎么回答更稳?
回答 MyBatis 问题时,可以按照:
是什么 + 为什么 + 怎么用 + 注意点
比如问:“#{} 和 ${} 有什么区别?”
可以这样回答:
#{} 是预编译占位符,底层使用 PreparedStatement,会把参数作为值处理,可以防止 SQL 注入。${} 是字符串拼接,会直接把参数拼接到 SQL 中,存在 SQL 注入风险。一般能用 #{} 就不用 ${},只有动态表名、字段名、排序字段这类场景才会用 ${},并且要做白名单校验。
再比如问:“Mapper 接口为什么不需要实现类?”
可以这样回答:
因为 MyBatis 会为 Mapper 接口创建动态代理对象。调用 Mapper 方法时,代理对象会根据接口全限定名和方法名,找到对应的 MappedStatement,也就是 XML 中配置的 SQL,然后执行 SQL 并封装结果返回。
总结
MyBatis 是 Java 后端项目中非常常用的持久层框架,面试高频内容主要包括:
MyBatis 基本概念 Mapper 动态代理 #{} 和 ${} SQL 注入 resultType 和 resultMap 动态 SQL 分页插件 一级缓存和二级缓存 延迟加载 MyBatis-Plus其中最重要的是:
- MyBatis 是什么。
- #{} 和 ${} 的区别。
- Mapper 接口为什么不需要实现类。
- resultType 和 resultMap 的区别。
- 动态 SQL 怎么写。
- 一级缓存和二级缓存的区别。
- MyBatis 和 MyBatis-Plus 的区别。
如果是初学者,建议先把 MyBatis 的基本 CRUD、动态 SQL、结果映射搞清楚,再去结合 Spring Boot 做一个完整项目。这样面试时不只是会背概念,也能结合实际开发场景回答。
