最近准备面试,整理了一些关于 MyBatis 的常见问题,结合技术细节进行了深入解答,分享出来供大家参考。以下是问题的详细解析。
1. 什么是 MyBatis?MyBatis 的结构是怎样的?
MyBatis 是什么?
MyBatis 是一个基于 Java 的半自动化 ORM 框架,通过 XML 或注解配置 SQL,支持自定义查询、存储过程和结果映射。它简化了数据库操作,消除了传统 JDBC 中繁琐的资源管理和结果处理代码。
MyBatis 的结构:
- 全局配置文件 (
mybatis-config.xml
):配置数据源、事务管理器、插件等。 - Mapper 文件 (
mapper.xml
):定义 SQL 语句和结果映射规则。 - SqlSessionFactory:通过配置文件构建的工厂类,单例模式,用于创建 SqlSession。
- SqlSession:线程不安全的会话对象,提供执行 SQL 和事务管理的接口。
- Mapper 接口:通过动态代理绑定 XML 中的 SQL,开发者只需调用方法即可。
- Executor:底层执行器,有 SimpleExecutor、ReuseExecutor 等类型,负责 SQL 执行。
- MappedStatement:封装了 SQL 语句、参数映射和结果映射的元数据。
2. 简述 MyBatis 的优缺点
优点:
- 支持自定义 SQL,灵活性高,适合复杂查询。
- 通过 XML 或注解配置,减少重复代码。
- 与 Spring 集成良好,支持动态 SQL。
缺点:
- SQL 需手动编写,工作量比全自动 ORM 大。
- 动态 SQL 和配置有一定学习成本。
- 大型项目中 SQL 文件管理可能复杂。
3. MyBatis 是怎么将 SQL 执行的结果封装为目标对象并返回的?
详细流程:
MyBatis 将 SQL 执行结果封装为目标对象的过程涉及多个组件和 Java 技术,以下是具体步骤:
- SQL 执行 :
- SqlSession 调用 Executor(如
SimpleExecutor
)执行 SQL。 - Executor 通过 JDBC 的
PreparedStatement
执行查询,返回ResultSet
。
- SqlSession 调用 Executor(如
- 元数据解析 :
- MyBatis 根据
mapper.xml
中的<resultMap>
或接口注解,解析列名与对象属性的映射关系。 - 使用
Configuration
对象存储全局配置和映射信息。
- MyBatis 根据
- 结果处理 :
DefaultResultSetHandler
(ResultSetHandler 的默认实现)接管结果集处理。- 通过
ResultSetWrapper
提取ResultSet
的元数据(如列名、类型)。
- 对象实例化 :
- 使用 Java 反射(
Reflector
和ObjectFactory
)创建目标对象实例(如newInstance()
)。
- 使用 Java 反射(
- 属性填充 :
- 根据
<resultMap>
的<result>
标签或默认映射规则,将ResultSet
中的值通过TypeHandler
转换为 Java 类型。 - 使用反射(
Field.set()
或 Setter 方法)填充到对象属性中。
- 根据
- 结果返回 :
- 单条记录返回单个对象,多条记录通过
DefaultResultHandler
封装为List
或其他集合。
- 单条记录返回单个对象,多条记录通过
用到的 Java 技术与对象:
- 反射:创建对象、设置属性值。
- JDBC:执行 SQL、获取 ResultSet。
- TypeHandler :处理数据库类型与 Java 类型转换(如
IntegerTypeHandler
)。 - ResultSetHandler:核心组件,负责映射和封装。
MyBatis 干了什么活?
MyBatis 屏蔽了 JDBC 的底层细节(如连接管理、参数设置、结果遍历),通过反射和映射机制实现了自动化对象封装。
4. 动态 SQL 的作用,具体编写步骤,有哪些动态 SQL?
作用:
动态 SQL 允许根据输入参数动态生成 SQL,增强代码复用性,适用于条件查询、批量操作等场景。
具体编写步骤:
- 定义基础 SQL :
在mapper.xml
中编写静态 SQL 框架,如SELECT * FROM user
。 - 添加动态条件 :
-
使用
<if>
标签根据参数拼接条件:xml<if test="username != null and username != ''"> AND username = #{username} </if>
-
AND/OR 的处理 :
<where>
标签会自动移除多余的 AND/OR,例如:xml<where> <if test="username != null">AND username = #{username}</if> <if test="age != null">AND age = #{age}</if> </where>
输出:
WHERE username = ? AND age = ?
(无多余 AND)。
-
-
使用
<set>
处理更新语句中的逗号:xml<set> <if test="username != null">username = #{username},</if> <if test="age != null">age = #{age},</if> </set>
输出:
SET username = ?, age = ?
(自动移除末尾逗号)。
-
- 循环处理 :
-
使用
<foreach>
遍历集合,如 IN 查询:xml<foreach collection="ids" item="id" open="(" close=")" separator=","> #{id} </foreach>
输出:
(1, 2, 3)
。
-
- 条件选择 :
-
使用
<choose>
实现类似 switch-case:xml<choose> <when test="type == 'A'">AND type = 'A'</when> <otherwise>AND type = 'B'</otherwise> </choose>
-
常见动态 SQL 标签:
<if>
:条件拼接。<where>
:自动清理多余 AND/OR。<set>
:自动清理多余逗号。<foreach>
:处理集合循环。<choose>
、<when>
、<otherwise>
:条件分支。
注解支持:
通过 @Select
等注解结合 OGNL 表达式实现动态 SQL,但功能有限,通常 XML 更常用。
5. #{} 和 ${} 的区别
#{}
:- 预编译占位符,生成
PreparedStatement
,如WHERE id = ?
。 - 防止 SQL 注入,参数值在执行时绑定。
- 适用于普通参数替换。
- 预编译占位符,生成
${}
:- 字符串拼接,直接嵌入 SQL,如
ORDER BY ${column}
。 - 有 SQL 注入风险,仅用于动态表名或列名。
- 不经过预编译处理。
- 字符串拼接,直接嵌入 SQL,如
建议 :优先用 #{}
,谨慎用 ${}
并做好参数校验。
6. 通常一个 mapper.xml 对应一个 DAO 接口,这个 DAO 接口的工作原理是什么?
详细内部过程:
MyBatis 的 DAO 接口无需手动实现,依赖动态代理自动生成实现类。以下是具体工作原理:
-
接口定义与绑定:
-
开发者定义 DAO 接口,如:
javapublic interface UserMapper { User getUserById(int id); }
-
在
mapper.xml
中,namespace
绑定接口全限定名,<select>
的id
与方法名对应:xml<mapper namespace="com.example.UserMapper"> <select id="getUserById" resultType="User"> SELECT * FROM user WHERE id = #{id} </select> </mapper>
-
-
代理对象创建:
- 调用
SqlSession.getMapper(UserMapper.class)
时,MyBatis 使用 JDK 动态代理(Proxy.newProxyInstance
)生成接口实现类。 - 代理对象由
MapperProxy
类管理,内部持有SqlSession
和接口的元数据。
- 调用
-
方法调用拦截:
- 调用接口方法(如
getUserById(1)
)时,触发MapperProxy.invoke()
。 - MyBatis 根据方法名和
namespace
在Configuration
中定位对应的MappedStatement
。
- 调用接口方法(如
-
SQL 执行:
MappedStatement
包含 SQL 和映射规则,交给SqlSession
的selectOne()
或selectList()
执行。Executor
调用 JDBC 执行 SQL,返回结果。
-
结果映射与返回:
ResultSetHandler
将结果映射为目标对象(如User
),返回给调用方。
用到的技术与对象:
- JDK 动态代理:生成代理对象。
- 反射:解析接口方法与 XML 映射。
- Configuration:存储全局配置和映射元数据。
- MapperProxy:代理实现的核心类。
MyBatis 干了什么活?
MyBatis 通过动态代理屏蔽了 DAO 实现细节,开发者只需定义接口和 XML,就能完成数据库操作。