适用读者:写了很多
@Select但从没想过"接口方法谁实现的"的人。本系列第一篇。
前置:S07-B03(AOP 代理)。
实测环境:MyBatis-Spring-Boot-Starter 3.0.3 / Spring Boot 3.3.6 / H2(2026-11-05)。
覆盖旧文:905(78) 496(38) 901(97) 906(74) 1749(88) + 空壳 1139/1148 补写 = 375 点。
覆盖扩展:E-S08-001(动态代理 + MappedStatement)、E-S08-002(Executor 三级)、E-S08-008/E-S08-009(空壳 1139 工作原理、1148 Spring 整合,已补写)。
下一篇:S08-B02 · 参数与结果映射。
1. 一句话结论
Mapper 接口的实现类是运行时用 JDK 动态代理生成的:代理拦截方法调用,用"接口全名 + 方法名"拼出 MappedStatement 的 id,再去执行对应的 SQL。
UserMapper.byNameSafe("alice")
→ 代理拦截
→ statementId = "demo.UserMapper.byNameSafe"
→ 找到 MappedStatement(SQL + 参数映射 + 结果映射)
→ Executor 执行 → 结果映射成 User
实测(一次真实调用):
2. #{} 传 name=alice → [User(1,alice,20)]
2. 为什么必须是接口(E-S08-001)
| 前提 | 原因 |
|---|---|
| 接口而不是类 | JDK 动态代理只能代理接口 |
| 方法签名 | 决定参数与返回类型 |
| 注解或 XML | 提供 SQL 与映射 |
@Mapper 做的事:让 MyBatis 的 MapperScannerConfigurer 扫描到它,注册成 Bean(实现就是代理)。
判据:"我的 Mapper 里能写 if 逻辑吗?" 不能——接口没有方法体。业务逻辑必须放 Service。
3. Executor 三级(E-S08-002、A-906)
| Executor | 行为 |
|---|---|
SIMPLE(默认) |
每次创建新的 Statement |
REUSE |
复用 Statement(同 SQL) |
BATCH |
批量执行,攒到 flushStatements 才发 |
切换方式:
mybatis:
executor-type: batch # simple / reuse / batch
判据:"要插入 1 万条吗?" 要 → BATCH(J04 会量化差异)。
4. 与 Spring 整合(A-1749、空壳 1148 补写)
| 组件 | 作用 |
|---|---|
SqlSessionFactoryBean |
构建 SqlSessionFactory |
@MapperScan |
批量注册 Mapper(替代逐个 @Mapper) |
SqlSessionTemplate |
线程安全的 SqlSession 封装 |
| 事务管理器 | MyBatis 参与 Spring 事务(S07-J01 的代理规则同样适用) |
关键点:MyBatis 的 SqlSession 与 Spring 事务绑定——所以"@Transactional 自调用失效"在 Mapper 层同样成立(Mapper 调用走的是 Spring 管理的代理)。
实测(spring-boot-starter 自动装配后):
1. #{} 传 age=10 → [User(1,alice,20), User(2,bob,30), User(3,carol,40)]
三步都没写任何 SqlSession 代码——mybatis-spring-boot-starter 自动装配完成。
5. 工作流程(A-901、空壳 1139 补写)
① 加载配置(XML + 注解)→ 构建 Configuration
② 解析 SQL → MappedStatement(id、参数映射、结果映射)
③ 调用 Mapper 方法 → 代理 → SqlSession.selectList(statementId, param)
④ Executor 执行:拼 SQL(预编译)→ 参数设置 → JDBC 执行
⑤ ResultSet → ResultMap → 对象
"为什么 Mapper 接口不用实现类"的完整答案:① ② 在启动时完成,③ 的代理在运行时把"方法调用"翻译成"statementId + 参数"。
6. 五个误区
| 误区 | 真相 |
|---|---|
| "Mapper 是自动生成的实现类" | 是动态代理,没有生成源码 |
| "Mapper 里可以写业务逻辑" | 接口没有方法体 |
| "MyBatis 与 Spring 事务无关" | 通过 SqlSessionTemplate 参与 Spring 事务 |
| "Executor 默认是 batch" | 默认 SIMPLE |
| "statementId 是随便起的" | 默认 接口全名.方法名,改名会破坏映射 |
7. 自检
- Mapper 的实现是怎么来的?为什么必须是接口?
- statementId 默认怎么构成?
- 三种 Executor 的区别?
@Transactional在 Mapper 层为什么同样受"代理"约束?- 业务逻辑为什么不能写在 Mapper 里?
8. 下一篇
S08-B02 · 参数与结果映射:#{} vs ${}
下一篇用真实注入串验证两者的差异——${} 会返回全表,#{} 返回 0 行(实测)。
本篇属于系列《S08 · MyBatis 持久层》。旧文来源:A-905/496/901/906/1749(空壳 1139/1148 补写);扩展点 E-S08-001/002。调用结果为 MyBatis 3.0.3 + H2 实测。