1006 字
约 3 分钟
8
S08-B01 · Mapper 为什么不用写实现类

适用读者:写了很多 @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. 自检

  1. Mapper 的实现是怎么来的?为什么必须是接口?
  2. statementId 默认怎么构成?
  3. 三种 Executor 的区别?
  4. @Transactional 在 Mapper 层为什么同样受"代理"约束?
  5. 业务逻辑为什么不能写在 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 实测。

S08-B01 · Mapper 为什么不用写实现类
https://www.clxhxhhr.top/posts/5282/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。