当前位置: 首页 > news >正文

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 常见优点:

  1. SQL 写在 XML 或注解中,灵活性高。
  2. 封装了 JDBC,减少重复代码。
  3. 支持动态 SQL。
  4. 支持结果映射。
  5. 学习成本相对较低。
  6. 方便进行 SQL 优化。
  7. 和 Spring Boot 整合方便。

相比全自动 ORM,MyBatis 更适合复杂 SQL 场景。


4. MyBatis 和 JDBC 有什么区别?

对比项JDBCMyBatis
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 的大致流程:

  1. 读取 MyBatis 配置文件。
  2. 加载 Mapper XML 文件。
  3. 创建 SqlSessionFactory。
  4. 创建 SqlSession。
  5. 通过 Mapper 代理对象调用方法。
  6. 找到对应的 SQL 语句。
  7. 设置 SQL 参数。
  8. 执行 SQL。
  9. 封装结果集。
  1. 返回 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 有什么区别?

对比项MyBatisHibernate
类型半自动 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

其中最重要的是:

  1. MyBatis 是什么。
  2. #{} 和 ${} 的区别。
  3. Mapper 接口为什么不需要实现类。
  4. resultType 和 resultMap 的区别。
  5. 动态 SQL 怎么写。
  6. 一级缓存和二级缓存的区别。
  7. MyBatis 和 MyBatis-Plus 的区别。

如果是初学者,建议先把 MyBatis 的基本 CRUD、动态 SQL、结果映射搞清楚,再去结合 Spring Boot 做一个完整项目。这样面试时不只是会背概念,也能结合实际开发场景回答。

http://www.jsqmd.com/news/774959/

相关文章:

  • Python配置管理实战:从环境变量到类型安全,详解Tanuki单文件库设计
  • #81_闲谈语言的分类
  • linux kernel CONFIG_KCMP解析
  • YOLOv11室内地面塑料袋目标检测数据集-30张-Plastic-Bag-1
  • 微信福音:2345清理王微信专清功能介绍
  • 告别GPIO模拟!用STM32的FSMC高效驱动TFT屏,刷图速度提升实测
  • 吃透C++ STL map/set:从入门到实战,新手也能轻松上手
  • 车载诊断架构---解答售后关于Service 19 06疑问带来的反思
  • 3203黄大年茶思屋榜文保姆级全落地解法「32期3题」量子启发式算法|大规模百万节点图平衡最小分割优化
  • 用Python+PuLP搞定钢管运输优化:手把手复现2000年数模国赛B题
  • 大语言模型如何构建创业者认知代理:从特征工程到RAG应用
  • dotnet-skills:让AI助手掌握现代.NET开发最佳实践
  • 欧拉回路(一笔画)
  • “灵语星火”第二阶段团队记录(一)
  • 如何在华为HarmonyOS设备上部署microG服务:解决签名验证的完整技术指南
  • 开源情报实战指南:从工具到体系的OSINT方法论与自动化实践
  • Emacs光标管理库cursory:实现情境感知的自动切换与主题集成
  • 轻量级唤醒词检测:从MFCC特征到CNN模型在边缘设备的实践
  • 基于工作流的低代码AI应用开发:Flock平台核心架构与实战指南
  • 为什么很多人 DFS 写得飞起,一到「矩阵最长递增路径」就彻底懵了?
  • [特殊字符] 数组中的递增三元组:O(n) 时间高效查找,面试必考!
  • “灵语星火”第二阶段团队记录(二)
  • 给Claude Code装个仪表盘 Claude HUD保姆级教程命令行也能直观可控
  • 告别纯寄存器:用STC-ISP工具图形化配置STC8H的PWM,5分钟生成代码
  • CUDA内核优化:从手工调优到AI驱动的自动化实践
  • 如何免费下载TIDAL高品质音乐:tidal-dl-ng完整使用教程
  • 明代裙装形制融入现代中国男装设计研究
  • python系列【仅供参考】:JS的解析与Js2Py使用
  • 通用网页内容提取器xungen:基于示例驱动的自动化数据抓取方案
  • 深度优化:2345清理王系统碎片清理功能详解