1、什么是 MyBatis?
核心解析
MyBatis 是一款半 ORM(对象关系映射)框架,本质是对 JDBC 的封装,解决 JDBC 开发中 “重复冗余代码” 的痛点。开发时只需关注 SQL 本身,无需手动处理 “加载驱动、创建连接、创建 Statement” 等繁琐步骤,同时支持通过 XML 或注解将 POJO(简单 Java 对象)与数据库记录映射,消除几乎所有 JDBC 冗余代码。
记忆要点
- 定位:半 ORM 框架,封装 JDBC;
- 核心价值:简化开发(免写 JDBC 冗余代码)、灵活可控(直接写原生 SQL);
- 映射方式:XML 或注解,关联 POJO 与数据库。
2、MyBatis 的优缺点?
核心解析
MyBatis 的优势和劣势均源于其 “手动控制 SQL” 的设计理念,具体对比如下:
维度 | 优点(核心:灵活、简化) | 缺点(核心:依赖 SQL) |
---|---|---|
SQL 控制 | 基于原生 SQL 编程,灵活度高,不影响现有系统 / 数据库设计;SQL 与代码分离(XML 配置),便于管理 | SQL 编写工作量大(字段多、关联表多时更明显),对开发人员 SQL 功底有要求 |
开发效率 | 比 JDBC 减少 50%+ 代码量,无需手动开关连接;支持动态 SQL、ORM 映射 | – |
兼容性 | 依赖 JDBC,支持所有 JDBC 兼容的数据库 | SQL 依赖数据库语法(如 MySQL 的LIMIT 、Oracle 的ROWNUM ),数据库移植性差 |
集成能力 | 可与 Spring 无缝集成 | – |
记忆要点
- 优点记 “3 减 2 支持”:减代码、减冗余、减耦合;支持动态 SQL、支持多数据库;
- 缺点记 “2 依赖”:依赖开发人员 SQL 能力、依赖具体数据库语法。
3、Hibernate 和 MyBatis 的区别?
核心解析
两者都是持久层框架,均封装 JDBC,但设计理念完全不同(“全自动” vs “半自动”),核心区别集中在映射粒度、SQL 控制、移植性三方面:
对比维度 | MyBatis(半自动 ORM) | Hibernate(全自动 ORM) |
---|---|---|
映射关系 | 映射 “Java 对象与 SQL 执行结果”,多表关联配置简单(需手动写关联 SQL) | 映射 “Java 对象与数据库表”,多表关联配置复杂(需配置对象间关联关系,如one-to-many ) |
SQL 控制 | 手动写原生 SQL,支持动态 SQL、存储过程,SQL 优化易(直接改 SQL) | 自动生成 SQL,提供 HQL(跨数据库查询语言),SQL 优化难(需理解 HQL 生成逻辑) |
数据库移植性 | 差(SQL 依赖数据库语法) | 好(HQL 跨数据库,无需改 SQL) |
功能封装 | 轻量,仅封装 JDBC 核心流程,无额外复杂特性(如级联较弱) | 重量级,封装完整(日志、缓存、强级联),但性能消耗略高 |
记忆要点
- 相同点:都封装 JDBC,都是持久层框架,用于 DAO 层;
- 不同点:MyBatis“手动 SQL + 结果映射”,Hibernate“自动 SQL + 表映射”;移植性 H 好,灵活性 My 好。
4、为什么说 MyBatis 是半自动 ORM 映射工具?与全自动的区别?
核心解析
ORM 的 “全自动 / 半自动” 核心判断标准是:查询关联对象时是否需要手动写 SQL。
- 全自动(如 Hibernate):根据对象关系模型直接获取关联对象。例如查询 “用户(User)” 时,若 User 关联 “订单(Order)”,调用
user.getOrders()
即可自动查询订单,无需手动写 SQL; - 半自动(MyBatis):查询关联对象必须手动编写 SQL。例如查询 “用户” 时,若要获取其关联的 “订单”,需在 XML 中写 “关联查询 SQL”(如
SELECT u.*, o.* FROM user u LEFT JOIN order o ON u.id = o.user_id
),或分两次查询(先查用户,再查订单)。
记忆要点
- 关键判断:关联对象查询是否需手动写 SQL;
- 对比:Hibernate“对象关系驱动,自动查关联”;MyBatis“SQL 驱动,手动查关联”。
5、传统 JDBC 开发存在什么问题?
核心解析
传统 JDBC 开发是 “手动操作全流程”,存在 4 个核心痛点,可按 “连接→SQL→参数→结果” 流程记忆:
- 连接管理混乱:频繁创建 / 释放数据库连接,浪费系统资源,影响性能(需手动实现连接池);
- SQL 硬编码:SQL 语句、参数设置、结果集处理写在 Java 代码中,SQL 变更需改代码、重新编译,维护成本高;
- 参数设置繁琐:
PreparedStatement
传参需手动对应 “?” 位置(如ps.setString(1, name)
),参数数量 / 顺序变了易出错; - 结果集处理重复:需手动遍历
ResultSet
,将字段值逐个赋给 POJO,代码重复且繁琐。
记忆要点
- 按 “连接→SQL→参数→结果” 四步记,每步对应一个痛点:连接浪费、SQL 硬编码、参数易错、结果处理繁。
6、JDBC 的不足,MyBatis 是如何解决的?
核心解析
MyBatis 针对 JDBC 的 4 个痛点,提供了 “配置化 + 自动化” 的解决方案,一一对应:
JDBC 痛点 | MyBatis 解决方案 |
---|---|
连接管理混乱 | 在mybatis-config.xml 中配置数据库连接池(如 Druid、C3P0),统一管理连接,避免频繁创建 / 释放 |
SQL 硬编码 | 将 SQL 写在XXXMapper.xml 中,与 Java 代码分离,SQL 变更只需改 XML,无需重新编译 |
参数设置繁琐 | 自动将 Java 对象(POJO/Map)映射到 SQL 的#{} , 无需手动对应 “?” 位置 |
结果集处理重复 | 自动将 SQL 执行结果映射到 Java 对象(POJO/List/Map),无需手动遍历ResultSet |
记忆要点
- 对应 JDBC 痛点,记 “4 自动 / 配置”:连接池配置、SQLXML 配置、参数自动映射、结果自动映射。
7、MyBatis 编程步骤是什么样的?
核心解析
MyBatis 编程是 “从工厂到会话,再到执行” 的流程,共 6 步,核心是SqlSessionFactory(工厂)→SqlSession(会话)→执行 SQL:
- 创建
SqlSessionFactory
(全局唯一,加载配置文件生成); - 通过
SqlSessionFactory
创建SqlSession
(单次数据库交互会话); - 通过
SqlSession
执行数据库操作(如selectOne()
、update()
); - 若执行增删改,调用
session.commit()
提交事务; - 调用
session.close()
关闭会话,释放资源;
记忆要点
- 记 “工厂造会话,会话做操作,操作完提交,最后关会话”;
- 关键:
SqlSessionFactory
是单例(全局一个),SqlSession
是多例(每次交互一个)。
8、说说 MyBatis 的工作原理?
核心解析
MyBatis 的工作原理是 “加载配置→创建工厂→创建会话→执行 SQL→映射结果” 的全流程,可拆解为 8 个关键步骤:
- 加载全局配置:读取
mybatis-config.xml
(全局配置文件),包含数据库连接信息、环境配置等; - 加载映射文件:读取
XXXMapper.xml
(SQL 映射文件),包含 SQL 语句、参数 / 结果映射规则,需在全局配置中指定加载路径; - 创建会话工厂:根据全局配置 + 映射文件,构建
SqlSessionFactory
(单例,负责生成SqlSession
); - 创建会话:
SqlSessionFactory
创建SqlSession
(会话对象,包含执行 SQL 的所有方法); - 获取执行器:
SqlSession
内部创建Executor
(执行器,MyBatis 核心组件,负责 SQL 执行和缓存维护); - 封装 MappedStatement:
Executor
根据 SQL 的 id,从配置中获取MappedStatement
(封装 SQL 的 id、参数类型、结果类型等信息); - 输入参数映射:
Executor
将 Java 参数(POJO/Map)转换为 SQL 的参数(类似 JDBC 的PreparedStatement
设参); - 输出结果映射:执行 SQL 后,
Executor
将ResultSet
转换为 Java 对象(POJO/List),返回给SqlSession
,最终返回给调用者。
记忆要点
- 按 “配置→工厂→会话→执行器→SQL 封装→参数映射→结果映射” 流程记;
- 核心组件:
SqlSessionFactory
(工厂)、SqlSession
(会话)、Executor
(执行器)、MappedStatement
(SQL 封装)。
9、MyBatis 的功能架构是怎样的?
核心解析
MyBatis 的功能架构按 “分层职责” 划分,共 3 层,自上而下为 “调用→处理→支撑”:
- API 接口层:提供外部调用接口(如
SqlSession
的selectOne()
、insert()
),开发人员通过该层操作数据库,接收到请求后转发给 “数据处理层”; - 数据处理层:核心业务层,负责 SQL 查找、解析、执行和结果映射,具体包括:
- 查找
MappedStatement
(根据 SQL id); - 解析动态 SQL(如
if
、foreach
); - 执行 SQL(通过
Executor
); - 结果映射(
ResultSet
→Java 对象);
- 查找
- 基础支撑层:提供通用基础功能,支撑上层运行,包括:
- 连接管理(连接池);
- 事务管理(提交 / 回滚);
- 配置加载(全局配置、映射文件);
- 缓存处理(一级 / 二级缓存)。
记忆要点
- 三层记 “上接口、中处理、下支撑”:接口层对外,处理层做核心,支撑层做基础。
10、什么是 DBMS?
核心解析
DBMS(Database Management System,数据库管理系统)是操纵和管理数据库的大型软件,是 “用户 / 应用程序” 与 “数据库” 之间的中间件,核心作用是 “统一管理数据库,保证数据安全和完整”。
- 核心功能:建立数据库、维护数据(增删改查)、权限控制、数据备份 / 恢复;
- 关键接口:提供 DDL(数据定义语言,如
CREATE TABLE
)和 DML(数据操作语言,如INSERT
、SELECT
),供用户操作数据库; - 典型例子:MySQL、Oracle、SQL Server。
记忆要点
- 定位:数据库的 “管家”,连接用户与数据库;
- 核心:提供 DDL/DML,管安全、管维护。
11、为什么需要预编译?
核心解析
SQL 预编译是 “数据库驱动在发送 SQL 和参数前,先将 SQL 编译为可执行的二进制格式”,核心目的是 “优化性能 + 防注入”,具体原因:
- 提升性能:DBMS 执行预编译后的 SQL 时,无需重新编译(复杂 SQL 编译耗时久),且预编译后的
PreparedStatement
可重复利用(缓存起来下次直接用),减少重复编译开销; - 防止 SQL 注入:预编译将 “SQL 语句” 与 “参数” 分离,参数会被当作 “纯值” 处理(而非 SQL 片段),避免恶意参数拼接成非法 SQL(如
OR 1=1
);
- 注意:MyBatis 默认对所有 SQL 进行预编译(通过
#{}
, 对应PreparedStatement
)。
记忆要点
- 预编译记 “2 个好处”:快(少编译)、安全(防注入);
- MyBatis 默认支持,对应
#{}
, 而非${}
。
12、MyBatis 都有哪些 Executor 执行器?它们之间的区别是什么?
核心解析
Executor
是 MyBatis 的核心执行器,负责 SQL 执行和 Statement 管理,共 3 种类型,区别集中在 “Statement 的创建与复用策略”:
执行器类型 | 核心逻辑 | 适用场景 |
---|---|---|
SimpleExecutor | 每执行一次 SQL(select/update),创建一个 Statement,执行完立即关闭 | 单次、少量 SQL 执行(默认类型) |
ReuseExecutor | 以 SQL 为 key 缓存 Statement,执行相同 SQL 时复用已有 Statement,不关闭 | 重复执行相同 SQL(如循环查同一类数据) |
BatchExecutor | 仅支持 update(不支持 select),将所有 SQL 加入批处理(addBatch ()),统一执行(executeBatch ()) | 批量更新 / 插入(如批量插入 1000 条数据) |
记忆要点
- 记 “3 种复用策略”:Simple(不用)、Reuse(复用同 SQL)、Batch(批量攒 SQL);
- Batch 仅支持增删改,不支持查。
13、MyBatis 中如何指定使用哪一种 Executor 执行器?
核心解析
指定 Executor 有 2 种方式:全局默认配置和手动指定会话级别:
- 全局默认配置:在mybatis-config.xml的<settings>中配置defaultExecutorType,指定全局默认执行器:xml<settings>
<!– 可选值:SIMPLE(默认)、REUSE、BATCH –>
<setting name=”defaultExecutorType” value=”SIMPLE”/>
</settings> - 手动指定会话级别:创建SqlSession时,通过SqlSessionFactory.openSession(ExecutorType)手动指定,优先级高于全局配置:java运行// 手动指定Batch执行器
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
记忆要点
- 两种方式:全局配
settings
,会话级传参数; - 优先级:会话级 > 全局级。
14、MyBatis 是否支持延迟加载?如果支持,它的实现原理是什么?
核心解析
MyBatis仅支持关联对象(一对一 association、一对多 collection)的延迟加载,核心是 “用的时候再查”,而非 “一次查完所有关联数据”。
- 启用配置:在
mybatis-config.xml
中设置lazyLoadingEnabled=true
(默认 false); - 实现原理(CGLIB 动态代理):
- 查询主对象时(如查 User),MyBatis 用 CGLIB 生成 User 的代理对象;
- 此时关联对象(如 User 的 Orders)为 null,不触发关联查询;
- 当调用关联对象的方法时(如
user.getOrders().size()
),代理对象的拦截器(invoke()
)触发; - 拦截器发现关联对象为 null,执行事先保存的 “关联查询 SQL”(查 Orders),将结果赋值给 User 的 Orders 属性;
- 继续执行
user.getOrders().size()
,返回正确结果。
记忆要点
- 支持范围:仅 association(一对一)、collection(一对多);
- 原理:CGLIB 代理 + 拦截器,“用的时候才查关联数据”。
15、#{} 和 ${} 的区别?
核心解析
#{}和${}
是 MyBatis 中两种参数占位符,核心区别是 “是否预编译”,直接影响安全性和使用场景:
对比维度 | #{}(占位符) | ${}(拼接符) |
---|---|---|
预编译处理 | 支持,将 SQL 中的 #{} 替换为 “?”,通过PreparedStatement 设参,属于预编译 | 不支持,直接将参数值拼接进 SQL,属于字符串替换,无预编译 |
安全性 | 能防止 SQL 注入(参数作为纯值处理,不解析为 SQL 片段) | 不能防止 SQL 注入(参数可能被拼接成非法 SQL,如OR 1=1 ) |
变量替换位置 | DBMS 内部(SQL 预编译后,DBMS 接收参数值) | DBMS 外部(MyBatis 在发送 SQL 前,已将参数拼接进 SQL) |
适用场景 | 普通参数(如 where 条件中的值:where id = #{id} ) | 动态 SQL 片段(如表名、排序字段:select * from ${tableName} order by ${field} ) |
记忆要点
- 记 “# 安全预编译,$ 拼接有风险”;
- 日常用 #{},动态表名 / 字段用 ${}(需确保参数安全)。
16、模糊查询 like 语句该怎么写?
核心解析
模糊查询需拼接 “%”,但需避免 SQL 注入,推荐 3 种写法(按安全性和兼容性排序):
- 推荐:使用 CONCAT () 函数(数据库兼容好,无注入风险)利用数据库的CONCAT()函数拼接 “%”,参数用#{}, 示例:xml<select id=”findUserByLikeName” resultType=”User”>
SELECT * FROM user WHERE name LIKE CONCAT(‘%’, #{name}, ‘%’)
</select> - 推荐:双引号包裹 %+#{}(注意引号,无注入风险)#{}会自动加单引号,因此 “%” 需用双引号包裹,避免被解析为字符串常量,示例:xml<select id=”findUserByLikeName” resultType=”User”>
SELECT * FROM user WHERE name LIKE “%”#{name}”%”
</select> - ** 不推荐:({}拼接**(有SQL注入风险) 直接用`){}` 拼接 “%”,参数可能被篡改,示例:xml<select id=”findUserByLikeName” resultType=”User”>
<!– 风险:若name为“’ OR 1=1 — ”,会拼接成非法SQL –>
SELECT * FROM user WHERE name LIKE ‘%${name}%’
</select> - 不推荐:bind 标签(需额外配置,灵活性低)用<bind>标签定义模糊匹配规则,示例:xml<select id=”findUserByLikeName” resultType=”User”>
<bind name=”pattern” value=”‘%’ + name + ‘%'”/>
SELECT * FROM user WHERE name LIKE #{pattern}
</select>
记忆要点
- 安全第一,优先用
CONCAT()
或 “双引号 +#{}”; - 绝对避免用
${}
做模糊查询(除非参数完全可控)。
17、在 mapper 中如何传递多个参数?
核心解析
MyBatis 传递多个参数有 4 种方式,按 “直观性 + 灵活性” 推荐排序:
方式 | 核心逻辑 | 示例代码 | 适用场景 |
---|---|---|---|
1. @Param 注解(推荐) | 在接口方法参数前加@Param("参数名") ,XML 中直接用参数名引用 | 接口:User selectUser(@Param("userName") String name, @Param("deptId") int deptId); XML:where user_name = #{userName} and dept_id = #{deptId} | 参数数量少(2-3 个),直观 |
2. Java Bean(推荐) | 封装多个参数为一个 POJO,接口方法参数为 POJO,XML 中用 POJO 属性名引用 | 接口:User selectUser(UserQuery query); (Query 含 userName、deptId) XML:where user_name = #{userName} and dept_id = #{deptId} | 参数数量多(>3 个),业务固定 |
3. Map(推荐) | 封装参数为 Map(key = 参数名,value = 参数值),XML 中用 key 引用 | 接口:User selectUser(Map<String, Object> params); 调用:params.put("userName", "张三"); params.put("deptId", 1); XML:where user_name = #{userName} and dept_id = #{deptId} | 参数易变、灵活传递 |
4. 顺序传参(不推荐) | 不做任何封装,XML 中用#{0}、#{1} 引用参数(0 代表第一个参数,1 代表第二个) | 接口:User selectUser(String name, int deptId); XML:where user_name = #{0} and dept_id = #{1} | 绝对避免(参数顺序变了就错) |
记忆要点
- 小参数用
@Param
,大参数用Bean
,灵活场景用Map
; - 绝对不用顺序传参(维护成本高)。
18、MyBatis 如何执行批量操作?
核心解析
MyBatis 批量操作(如批量插入、批量更新)有 3 种核心方式,按 “性能 + 兼容性” 推荐排序:
方式 | 核心逻辑 | 示例代码(以批量插入为例) | 适用场景 |
---|---|---|---|
1. foreach 标签(MySQL 推荐) | 利用 MySQL 的VALUES (), (), () 语法,通过<foreach> 遍历集合,拼接多组值 | 接口:int batchInsert(@Param("users") List<User> users); XML: INSERT INTO user(name, age) VALUES <foreach collection="users" item="user" separator=","> (#{user.name}, #{user.age}) </foreach> | MySQL 环境,批量插入 / 更新,性能好 |
2. ExecutorType.BATCH(推荐) | 手动指定ExecutorType.BATCH 执行器,SQL 会被批量攒批,统一提交 | Java 代码: SqlSession session = factory.openSession(ExecutorType.BATCH); UserMapper mapper = session.getMapper(UserMapper.class); for (User u : users) { mapper.insert(u); } session.commit(); XML:普通单条插入 SQL | 跨数据库(MySQL/Oracle),批量操作量大 |
3. 多 SQL 分隔(不推荐) | 开启 JDBC 的allowMultiQueries=true ,通过<foreach> 拼接多条 SQL(用;分隔) | JDBC URL:jdbc:mysql://localhost:3306/db?allowMultiQueries=true XML: <foreach collection="users" item="user" separator=";"> INSERT INTO user(name, age) VALUES(#{user.name}, #{user.age}) </foreach> | 兼容性差(需额外配置),性能一般 |
记忆要点
- MySQL 优先用
foreach+VALUES
,跨库或大数据量用BATCH
执行器; - 多 SQL 分隔需开启
allowMultiQueries
,不推荐。
19、如何获取生成的主键?
核心解析
MyBatis 支持获取 “自增主键”(如 MySQL 的AUTO_INCREMENT
),核心是通过useGeneratedKeys
和keyProperty
配置,让 MyBatis 自动将生成的主键赋值给 POJO 的属性:
- 配置方式:在
<insert>
标签中添加useGeneratedKeys="true"
(启用主键生成)和keyProperty="主键属性名"
(指定 POJO 中存储主键的属性); - 示例代码:xml<!– 插入用户,获取自增的userId –>
<insert id=”insertUser” useGeneratedKeys=”true” keyProperty=”userId”>
INSERT INTO user(user_name, age) VALUES(#{userName}, #{age})
</insert>java运行// 调用后,user.getUserId()即可获取生成的主键
User user = new User();
user.setUserName(“张三”);
user.setAge(20);
userMapper.insertUser(user);
System.out.println(“生成的主键:” + user.getUserId()); // 如1001
记忆要点
- 关键配置:
useGeneratedKeys=true
(开)+keyProperty="POJO主键属性"
(存); - 注意:主键会赋值给传入的 POJO 对象,而非返回值(返回值是影响行数)。
20、当实体类中的属性名和表中的字段名不一样,怎么办?
核心解析
属性名与字段名不一致时,MyBatis 无法自动映射,需通过 “别名” 或 “resultMap” 解决,共 2 种方式:
方式 | 核心逻辑 | 示例代码 | 适用场景 |
---|---|---|---|
1. SQL 别名(简单) | 在 SQL 中给字段起别名,让别名与 POJO 属性名一致 | 接口:Order getOrderById(int id); XML: select order_id as id, order_no as orderNo, order_price as price from orders where order_id=#{id}; | 字段少、临时映射 |
2. resultMap(推荐) | 定义<resultMap> ,明确映射 “表字段名(column)” 与 “POJO 属性名(property)” | XML: <select id="getOrderById" parameterType="int" resultMap="orderMap"> select * from orders where order_id=#{id} </select> <resultMap type="Order" id="orderMap"> <id property="id" column="order_id"/> <!-- 主键映射 --> <result property="orderNo" column="order_no"/> <result property="price" column="order_price"/> </resultMap> | 字段多、多次复用映射规则 |
记忆要点
- 简单场景用 SQL 别名,复杂场景用
resultMap
; resultMap
中,<id>
映射主键,<result>
映射普通字段。
21、Mapper 编写有哪几种方式?
核心解析
Mapper(DAO 层)的编写方式按 “简化程度” 演进,共 3 种,目前主流用 “Mapper 扫描器”:
方式 | 核心逻辑 | 关键步骤 | 适用场景 |
---|---|---|---|
1. 接口实现类(继承 SqlSessionDaoSupport) | 编写 Mapper 接口 + 接口实现类(继承SqlSessionDaoSupport ) + Mapper.xml,通过getSqlSession() 执行 SQL | 1. 写接口:UserMapper ; 2. 写实现类:UserMapperImpl extends SqlSessionDaoSupport ; 3. Spring 配置实现类:<bean id="userMapper" class="xxx.UserMapperImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> | 早期方式,繁琐,几乎不用 |
2. MapperFactoryBean | 编写 Mapper 接口 + Mapper.xml,通过 Spring 的MapperFactoryBean 创建接口代理对象 | 1. 写接口和 XML(namespace = 接口全路径); 2. Spring 配置:<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="xxx.UserMapper"/> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> | 单 Mapper 配置,多 Mapper 时繁琐 |
3. Mapper 扫描器(推荐) | 编写 Mapper 接口 + Mapper.xml(接口名与 XML 名一致,同目录),通过MapperScannerConfigurer 批量扫描接口 | 1. 写接口和 XML(namespace = 接口全路径,文件名一致); 2. Spring 配置扫描器:<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="xxx.mapper"/> <!-- 扫描Mapper接口包 --> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> | 主流方式,批量创建,简洁 |
记忆要点
- 目前只用 “Mapper 扫描器”,核心是 “接口 + XML 同目录同名,配置扫描包”;
- 前两种方式已过时,了解即可。
22、什么是 MyBatis 的接口绑定?有哪些实现方式?
核心解析
接口绑定是 MyBatis 的核心特性:定义 Mapper 接口,将接口方法与 XML 中的 SQL 通过 “namespace+id” 绑定,调用接口方法即可执行对应 SQL,无需编写接口实现类(MyBatis 动态代理生成)。
- 核心价值:替代传统 JDBC 的
SqlSession.selectOne("namespace.id")
,更类型安全、更简洁; - 实现方式(2 种):
- XML 绑定(推荐):Mapper.xml 的namespace必须等于接口全路径,接口方法名必须等于 XML 中 SQL 的id,示例:
- 接口:
public interface UserMapper { User selectById(int id); }
- XML:
<mapper namespace="xxx.mapper.UserMapper"> <select id="selectById" parameterType="int" resultType="User"> select * from user where id=#{id} </select> </mapper>
- 接口:
- 注解绑定(简单 SQL):无需 XML,直接在接口方法上用@Select/@Insert/@Update/@Delete注解写 SQL,示例:java运行public interface UserMapper {
@Select(“select * from user where id=#{id}”)
User selectById(int id);
}
- XML 绑定(推荐):Mapper.xml 的namespace必须等于接口全路径,接口方法名必须等于 XML 中 SQL 的id,示例:
记忆要点
- 接口绑定:接口方法与 SQL 绑定,代理生成实现类;
- 实现:复杂 SQL 用 XML(namespace+id),简单 SQL 用注解。
23、使用 MyBatis 的 mapper 接口调用时有哪些要求?
核心解析
Mapper 接口调用的本质是 “MyBatis 生成动态代理对象”,需满足 4 个核心要求,否则代理对象无法匹配 SQL:
- 方法名与 SQL id 一致:接口方法名必须等于 Mapper.xml 中
<select>/<insert>
等标签的id
; - 参数类型与 parameterType 一致:接口方法的输入参数类型必须等于 XML 中
parameterType
指定的类型(若省略parameterType
,MyBatis 会自动推断); - 返回类型与 resultType/resultMap 一致:接口方法的返回类型必须等于 XML 中
resultType
(单对象 / 基本类型)或resultMap
(自定义映射)指定的类型; - XML 的 namespace 与接口全路径一致:Mapper.xml 的
namespace
必须是 Mapper 接口的 “包名 + 接口名”(如com.xxx.mapper.UserMapper
)。
记忆要点
- 四一致:方法名 = id,参数类型 = parameterType,返回类型 = resultType,namespace = 接口全路径。
24、Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?
核心解析
(1)Dao 接口的工作原理
Dao 接口(即 Mapper 接口)本身没有实现类,MyBatis 通过JDK 动态代理生成接口的代理对象(Proxy
),代理对象的核心逻辑:
- 当调用接口方法(如
userMapper.selectById(1)
)时,代理对象拦截该调用; - 代理对象根据 “接口全路径 + 方法名”(如
com.xxx.mapper.UserMapper.selectById
),从Configuration
中找到对应的MappedStatement
(封装 SQL 的对象); - 执行
MappedStatement
对应的 SQL,处理参数和结果映射; - 将结果返回给调用者。
(2)Dao 接口方法能否重载?
不能重载,原因是 MyBatis 查找 SQL 的 key 是 “namespace + 方法名”,与参数无关:
- 例如:接口中定义
User selectUser(String name)
和User selectUser(int id)
,两者的 “namespace + 方法名” 都是com.xxx.mapper.UserMapper.selectUser
,MyBatis 无法区分,会报 “SQL id 重复” 错误。
记忆要点
- 工作原理:JDK 动态代理,拦截方法,按 “namespace + 方法名” 找 SQL;
- 不能重载:key 是 “namespace + 方法名”,与参数无关,无法区分。
25、MyBatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?
核心解析
能否重复取决于是否配置namespace
,核心原因是 MyBatis 存储 SQL 的 key 是 “namespace+id”:
- 配置了 namespace:id 可以重复。 例如:
UserMapper.xml
(namespace=com.xxx.UserMapper)有id="selectById"
,OrderMapper.xml
(namespace=com.xxx.OrderMapper)也有id="selectById"
,两者的 key 分别是com.xxx.UserMapper.selectById
和com.xxx.OrderMapper.selectById
,不冲突; - 未配置 namespace:id 不能重复。 此时 MyBatis 存储 SQL 的 key 就是
id
,若不同 XML 的 id 重复,后加载的 SQL 会覆盖先加载的,导致数据错误。
记忆要点
- 有 namespace:id 可重复(key 是 namespace+id);
- 无 namespace:id 不可重复(key 是 id);
- 最佳实践:必须配置 namespace(对应 Mapper 接口全路径)。
26、简述 MyBatis 的 Xml 映射文件和 Mybatis 内部数据结构之间的映射关系?
核心解析
MyBatis 加载 Xml 映射文件后,会将 XML 中的标签解析为对应的 Java 对象,存储在Configuration
(全局配置对象)中,核心映射关系如下:
Xml 映射文件标签 | MyBatis 内部数据结构(Java 对象) | 作用 |
---|---|---|
<parameterMap> | ParameterMap | 封装 “参数映射规则”(已过时,推荐直接用parameterType +#{}) |
<parameterMap> 子元素 | ParameterMapping | 封装单个参数的映射信息(如参数名、JDBC 类型) |
<resultMap> | ResultMap | 封装 “结果映射规则”(表字段与 POJO 属性的对应关系) |
<resultMap> 子元素 | ResultMapping | 封装单个结果的映射信息(如 POJO 属性名、表字段名、JDBC 类型) |
<select>/<insert>/<update>/<delete> | MappedStatement | 封装单个 SQL 的完整信息(SQL 语句、参数类型、结果类型、缓存配置等) |
标签内的 SQL 语句 | BoundSql | 封装解析后的 SQL 语句、参数占位符信息(如#{}对应的参数名 ) |
记忆要点
- 核心映射:标签→对象,存储在
Configuration
; - 关键对象:
MappedStatement
(SQL 整体)、ResultMap
(结果映射)、BoundSql
(解析后 SQL)。
27、MyBatis 映射文件中,如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在 A 标签的前面?
核心解析
B 标签可以定义在任何位置(A 前面或后面),MyBatis 的解析逻辑支持 “先标记,后解析”:
- MyBatis 按顺序解析 Xml 文件,当解析到 A 标签时,发现 A 引用了 B 标签;
- 若此时 B 标签尚未解析(B 在 A 后面),MyBatis 将 A 标签标记为 “未解析状态”,继续解析后续标签;
- 当解析到 B 标签时,正常解析 B 并存储;
- 所有标签解析完毕后,MyBatis 重新解析那些 “未解析状态” 的标签(如 A),此时 B 已存在,A 可正常引用 B 完成解析。
记忆要点
- 引用标签位置无限制,MyBatis 支持 “先标记后解析”。
28、MyBatis 能执行一对多,一对一的联系查询吗,有哪些实现方法?
核心解析
MyBatis完全支持一对一、一对多、多对多的关联查询,核心通过resultMap
的association
(一对一)和collection
(一对多 / 多对多)标签实现,具体方法:
关联类型 | 实现方法 | 核心标签 / 逻辑 |
---|---|---|
一对一(如 User→IdCard) | 1. 嵌套查询(分步查):先查 User,再查 IdCard; 2. 嵌套结果(关联查):一次查 User 和 IdCard,用association 映射 | 嵌套结果示例: <resultMap type="User" id="userMap"> <id property="id" column="user_id"/> <association property="idCard" javaType="IdCard"> <id property="cardId" column="card_id"/> </association> </resultMap> |
一对多(如 User→Order) | 1. 嵌套查询(分步查):先查 User,再查其所有 Order; 2. 嵌套结果(关联查):一次查 User 和所有 Order,用collection 映射 | 嵌套结果示例: <resultMap type="User" id="userMap"> <id property="id" column="user_id"/> <collection property="orders" ofType="Order"> <id property="orderId" column="order_id"/> </collection> </resultMap> |
多对多(如 User→Role) | 本质是 “两个一对多”(User→UserRole→Role),通过中间表关联,用collection 嵌套查询 / 结果 | 逻辑:User 关联 UserRole(一对多),UserRole 关联 Role(一对多),最终 User→List<Role> |
记忆要点
- 一对一用
association
,一对多用collection
; - 实现方式分 “嵌套查询(分步)” 和 “嵌套结果(一次查)”。
29、MyBatis 是否可以映射 Enum 枚举类?
核心解析
可以映射 Enum 枚举类,MyBatis 支持通过 “自定义 TypeHandler(类型处理器)” 实现 Enum 与数据库字段的映射。
- TypeHandler 的核心作用:完成 “Java 类型(Enum)” 与 “JDBC 类型(如 VARCHAR/INT)” 的双向转换;
- 实现步骤:
- 自定义 TypeHandler,实现org.apache.ibatis.type.TypeHandler接口,重写 2 个核心方法:
setParameter()
:将 Enum 转换为 JDBC 类型(如将Sex.MALE
转换为字符串 “MALE”);getResult()
:将 JDBC 类型转换为 Enum(如将数据库的 “FEMALE” 转换为Sex.FEMALE
);
- 在 MyBatis 配置中注册 TypeHandler(或在
resultMap
/parameterType
中指定); - 在 Mapper.xml 中使用该 TypeHandler 映射 Enum 属性。
- 自定义 TypeHandler,实现org.apache.ibatis.type.TypeHandler接口,重写 2 个核心方法:
记忆要点
- 可以映射,需自定义 TypeHandler;
- 核心方法:
setParameter()
(Java→JDBC)、getResult()
(JDBC→Java)。
30、MyBatis 动态 sql 是做什么的?都有哪些动态 sql?能简述一下动态 sql 的执行原理吗?
核心解析
(1)动态 SQL 的作用
动态 SQL 是 MyBatis 的核心特性,用于在 Xml 中通过标签实现 “逻辑判断 + SQL 动态拼接”,解决 “SQL 条件不固定” 的问题(如查询时,条件可能有也可能没有)。
(2)动态 SQL 标签(共 9 种)
标签类别 | 标签 | 作用 |
---|---|---|
逻辑判断 | <if> | 单条件判断(如if test="name != null"> and name = #{name}</if> ) |
分支判断 | <choose> +<when> +<otherwise> | 多条件分支(类似 Java 的switch-case ) |
SQL 拼接优化 | <where> | 自动处理 “多余的 AND/OR”(如条件都不满足时不生成 WHERE,条件前有 AND 时自动删除) |
<set> | 自动处理 “多余的逗号”(用于 UPDATE,如set <if test="name != null">name=#{name},</if> → 自动删逗号) | |
<trim> | 自定义拼接规则(如trim prefix="WHERE" prefixOverrides="AND" → 同 where 标签) | |
循环 | <foreach> | 遍历集合生成 SQL(如in 条件:where id in <foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach> ) |
其他 | <bind> | 定义变量(如模糊查询:bind name="pattern" value="'%' + name + '%'" ) |
(3)执行原理
- MyBatis 加载动态 SQL 标签时,会将其解析为对应的 “动态 SQL 节点”(如
IfNode
、ForEachNode
); - 执行 SQL 前,MyBatis 使用OGNL 表达式(Object-Graph Navigation Language)从参数对象中计算标签的判断条件(如
if test="name != null"
→ 判断参数中 name 是否非空); - 根据 OGNL 计算结果,动态拼接 SQL(如条件为 true 则保留
<if>
内的 SQL 片段,false 则剔除); - 拼接完成后,生成最终可执行的 SQL,执行并处理结果。
记忆要点
- 作用:逻辑判断 + 动态拼 SQL;
- 标签:记 “3 判断(if/choose/trim)、2 优化(where/set)、1 循环(foreach)、1 绑定(bind)”;
- 原理:OGNL 算条件,动态拼 SQL。
31、MyBatis 是如何进行分页的?分页插件的原理是什么?
核心解析
(1)MyBatis 的分页方式
MyBatis 有 2 种分页方式,核心区别是 “内存分页vs物理分页”:
- 内存分页(RowBounds):
- 原理:先查询所有数据到内存,再通过
RowBounds
(offset
偏移量、limit
条数)截取结果; - 缺点:数据量大时内存占用高,性能差;
- 示例:
List<User> list = sqlSession.selectList("selectAll", null, new RowBounds(0, 10));
(查前 10 条);
- 原理:先查询所有数据到内存,再通过
- 物理分页:
- 原理:在 SQL 中直接添加分页语法(如 MySQL 的
LIMIT
、Oracle 的ROWNUM
),让数据库只返回分页数据; - 实现方式:
- 手动写分页 SQL:
select * from user limit #{offset}, #{limit}
; - 使用分页插件(如 PageHelper):自动拦截 SQL 并添加分页语法。
- 手动写分页 SQL:
- 原理:在 SQL 中直接添加分页语法(如 MySQL 的
(2)分页插件的原理(以 PageHelper 为例)
分页插件基于 MyBatis 的插件机制,核心是 “拦截 SQL→重写 SQL”:
- 配置分页插件,指定拦截的接口(如
Executor
的query
方法); - 当执行查询方法时,插件拦截该调用,获取待执行的 SQL(如
select * from user
); - 插件根据 “数据库方言”(如 MySQL/Oracle),动态重写 SQL,添加分页语法:
- MySQL:
select * from user
→select t.* from (select * from user) t limit 0, 10
; - Oracle:
select * from user
→select * from (select u.*, rownum rn from user u where rownum <= 10) where rn > 0
;
- MySQL:
- 执行重写后的 SQL,返回分页结果(含总条数、当前页数据等)。
记忆要点
- MyBatis 分页:内存分页(RowBounds,差)、物理分页(手动 SQL / 插件,好);
- 插件原理:拦截 SQL→按方言重写 SQL(加分页语法)。
32、简述 MyBatis 的插件运行原理,以及如何编写一个插件?
核心解析
(1)插件运行原理
MyBatis 的插件是 “基于 JDK 动态代理的接口拦截器”,仅能拦截 4 个核心接口的方法:Executor
(执行器)、StatementHandler
(SQL 语句处理器)、ParameterHandler
(参数处理器)、ResultSetHandler
(结果处理器),原理:
- 配置插件时,MyBatis 为这 4 个接口生成动态代理对象;
- 当调用接口的方法(如
Executor.query()
)时,代理对象拦截该调用; - 执行插件的
intercept()
方法(自定义逻辑,如修改 SQL、记录日志); - 插件逻辑执行完毕后,调用原接口方法,完成 SQL 执行。
(2)编写插件的步骤(以 “SQL 执行日志插件” 为例)
- 实现 Interceptor 接口:重写 3 个方法:
intercept(Invocation invocation)
:核心拦截逻辑(如打印 SQL);plugin(Object target)
:生成代理对象(一般用Plugin.wrap(target, this)
);setProperties(Properties properties)
:读取插件配置的属性(如日志开关);
type = Executor.class, // 拦截的接口
method = “query”, // 拦截的方法
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} // 方法参数
)})
public class SqlLogPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 1. 获取MappedStatement(含SQL)
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String sql = ms.getSqlSource().getBoundSql(invocation.getArgs()[1]).getSql();
// 2. 打印SQL日志
System.out.println(“执行SQL:” + sql);
// 3. 执行原方法
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 生成代理对象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 读取配置属性(如properties.getProperty(“logEnable”))
}
} - 配置插件:在mybatis-config.xml中注册插件:xml<plugins>
<plugin interceptor=”com.xxx.plugin.SqlLogPlugin”>
<!– 可选:配置插件属性 –>
<property name=”logEnable” value=”true”/>
</plugin>
</plugins>
记忆要点
- 运行原理:JDK 动态代理,拦截 4 个核心接口的方法;
- 编写步骤:实现 Interceptor + 加 @Intercepts 注解 + 配置插件。
33、MyBatis 的一级、二级缓存?
核心解析
MyBatis 的缓存是 “本地缓存”,用于减少数据库查询次数,提升性能,分一级和二级缓存,核心区别是 “作用域”:
对比维度 | 一级缓存(默认开启) | 二级缓存(默认关闭) |
---|---|---|
作用域 | SqlSession(会话):一个 SqlSession 内有效 | Mapper(Namespace):同一个 Namespace 下的所有 SqlSession 共享 |
存储实现 | 默认PerpetualCache (基于 HashMap) | 默认PerpetualCache ,可自定义(如 Ehcache、Redis) |
开启方式 | 无需配置,默认开启 | 1. 全局配置:mybatis-config.xml 中lazyLoadingEnabled=true ; 2. Mapper 配置:在<mapper> 中加<cache/> ; 3. POJO 实现Serializable 接口(缓存序列化) |
失效场景 | 1. SqlSession commit/close; 2. 执行增删改(CUD)操作; 3. 手动调用clearCache() | 1. 同一 Namespace 下执行 CUD 操作; 2. 手动调用clearCache() ; 3. 缓存过期(若配置) |
适用场景 | 单次会话内的重复查询(如同一请求中多次查同一用户) | 多会话共享的重复查询(如多个请求查同一商品列表) |
记忆要点
- 一级缓存:Session 级,默认开,CUD/close 失效;
- 二级缓存:Namespace 级,默认关,需配
<cache/>
+POJO 序列化,CUD 失效; - 核心:缓存是本地的,分布式场景需用 Redis 等分布式缓存。