- 1WEB 基础HTTP / Browser / WebServer静态页面写死,人人看到一样
- 2Servlet手写 if-else 分发不可扩展,无生命周期/并发管理
- 3JSP:参数散字符串参数散成一堆 String,取值靠字段名硬记
- 3JSP:scriptlet 塞逻辑scriptlet 把逻辑塞进页面,代码与 HTML 糊在一起
- 3JavaBean:数据对象化对象全靠 new、层层硬依赖
- 4Spring IoCWeb 路由仍手写 web.xml/if-else、手动取参绑定,展示/业务/数据访问没分开
- 5MVC · SpringMVC数据在库里,访问是重复样板 + 拼串有注入风险
- 6MySQLJava 连库要手写五步,连接与资源管理全手动
- 7JDBCSQL 与 Java 混写、结果集逐行手映射、没有动态 SQL
- 8MyBatis四份配置 + web.xml 装配繁琐,部署要外部 Tomcat
- 9SpringBoot链尾 · 不考核
MyBatis
MyBatis
目标:掌握 MyBatis「Mapper 接口 + XML 映射」的持久层开发,理解从 DAO 方法调用到结果集映射的全链路,能写出动态 SQL 与 resultMap。
本模块引入 ORM 框架 MyBatis:用
@Mapper接口写 DAO、用 XML 写 SQL 与结果映射,由 MyBatis 生成动态代理对象把接口方法执行成真实的数据库操作。以真实工程GuestBookSSM为样板(WordDao + WordMapper.xml + mybatis-config.xml)。
一、MyBatis 是什么
- 持久层框架:把「写 SQL」和「写 Java 接口」分离——接口只声明方法,SQL 写在 XML 里;
- 动态代理:MyBatis 为每个 Mapper 接口生成一个代理对象,调用接口方法时由它去执行对应 SQL;
- 结果映射:把查询返回的列(ResultSet)按
resultMap/resultType填进 Java 对象。
对照
GuestBookSSM:WordDao(@Repository @Mapper 接口)+WordMapper.xml(SQL + resultMap)+mybatis-config.xml(typeAliases、注册映射文件)。
二、一次查询的六步全链路
调用 wordDao.selectById(id),MyBatis 内部要走过这样一条链:
call → proxy → locate → bind → execute → map
- call:业务代码调用 Mapper 接口方法;
- proxy:MyBatis 动态代理拦截这次调用;
- locate:按
namespace + 方法名定位到 XML 里对应的<select id>; - bind:把
#{}参数绑定进 PreparedStatement; - execute:执行 SQL,得到 ResultSet;
- map:按 resultMap/resultType 把行数据映射成对象。
下面交互实验逐步看这条链(对照 GuestBookSSM 的 WordMapper.xml):
交互实验:MyBatis 一次查询全链路(MyBatisMapperFlow)
先选一个 DAO 方法,再点「下一步」,看一次 wordDao.findAll() 如何从接口调用一路走到 Java 对象。对照真实工程 GuestBookSSM(WordDao + WordMapper.xml)。
调用 DAO 接口 —— 负责组件 WordDao.findAll()
应用层像调普通 Java 方法一样调 wordDao.findAll()——接口里没有实现类,却能返回数据
#{...}:预编译占位符,MyBatis 把它转成 PreparedStatement 的 ?,参数与 SQL 分离,天然防 SQL 注入;${...}:字符串直接拼接进 SQL,用于动态表名/列名等无法预编译的场景,但必须校验输入,否则有注入风险。
底层走的仍是 JDBC:MyBatis 只是把「拼 SQL、绑参数、执行、取结果」这些重复动作封装好了。想看清 JDBC 本身,见『JDBC』章(模块7):
交互实验:JDBC 五步时序图(JDBCSequenceDiagram)
接口 · 多态JDBC 里 Driver / Connection / Statement / ResultSet 都是接口,MySQL 驱动提供具体实现。 下面按「加载驱动 → 建立连接 → 创建语句 → 执行 SQL → 结果与关闭」逐步高亮每条消息, 看调用如何在各对象之间流动、运行期如何落到具体实现。
接口与具体实现(多态示例)
| 接口 | MySQL 驱动实现 | 作用 |
|---|---|---|
| Driver | com.mysql.jdbc.Driver | 连接数据库的入口,负责自注册 |
| Connection | ConnectionImpl | 一条到数据库的连接会话 |
| Statement | StatementImpl | 执行 SQL、拿结果集的句柄 |
| ResultSet | ResultSetImpl | 查询返回的行集,可逐行遍历 |
经典 JDBC 五步代码
// ① 加载驱动:触发其 static 块完成自注册
Class.forName("com.mysql.jdbc.Driver");
// ② 建立连接:DriverManager 按 URL 选中具体驱动实现(多态)
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mytest", "root", "");
// ③ 创建语句
Statement stmt = conn.createStatement();
// ④ 执行 SQL
stmt.executeUpdate("insert into guestbook(username,title) values('tom','from tom')");
ResultSet rs = stmt.executeQuery("select * from guestbook");
// ⑤ 处理结果 + 关闭(顺序与创建相反)
while (rs.next()) {
System.out.println(rs.getInt("id") + " " + rs.getString("username"));
}
rs.close();
stmt.close();
conn.close();JDBC / 多态要点
- JDBC 的核心是接口:Driver / Connection / Statement / ResultSet 都是接口,厂商提供具体实现——编译期依赖接口、运行期由 DriverManager 按 URL 选中实现,正是模块2 学的多态落到真实 API;
- 五步时序:加载驱动 → 建立连接 → 创建语句 → 执行 SQL → 处理结果并关闭;资源释放顺序与创建相反(先 rs/stmt,再 conn);
- Class.forName 只负责触发驱动类的 static 块完成自注册;JDBC 4.0 起可由 SPI 自动发现而省略,但显式写出更稳妥、便于定位问题;
- 拼接 SQL 字符串有 SQL 注入风险:应使用 PreparedStatement 预编译 + 占位符 ?,把数据与命令分离(见下一节)。
三、# 与 $:两种占位符
#{}:预编译占位符——MyBatis 转成?,参数绑定进 PreparedStatement,防 SQL 注入(首选);${}:字符串直接拼接——用于动态表名/列名/排序字段这类无法参数化的场景,有注入风险。
GuestBookSSM的查询全部用#{},例如where g.id = #{id}。
四、动态 SQL:<if> / <where> / <foreach>
条件不固定时,用动态 SQL 标签按条件拼出最终 SQL:
<if test="...">:条件成立才拼接对应片段;<where>:自动处理首个and/or前缀(避免手写where and ...);<foreach>:遍历集合生成IN (...)或批量插入的多个 values。
下面交互实验实时看拼接效果(对照留言搜索场景):
交互实验:动态 SQL 实时拼接(DynamicSQLExplorer)
开关下面的条件,看 <where> / <if> 如何实时拼出最终 SQL;再增删批量插入的行,看 <foreach> 如何生成 values。
select g.id,g.title,g.contents,u.username
from guestbook g, users u where g.userid=u.id
where g.title like concat('%', #{title}, '%')insert into guestbook (title, contents) values
(#{w.title}, #{w.contents}), (#{w.title}, #{w.contents}), (#{w.title}, #{w.contents})五、结果映射:resultType vs resultMap
- resultType:列名与属性名一致时自动映射(可开
mapUnderscoreToCamelCase处理下划线↔驼峰); - resultMap:显式声明每条
column → property,用于列名≠属性名、以及<association>/<collection>关联对象。
GuestBookSSM 的 WordMapper.xml 里用 WordUser resultMap,把查询出的用户信息映射进关联对象 user:
交互实验:列 ↔ 属性映射(ResultMapVisualizer)
点击左侧任一数据库列,看它经 resultMap 落到哪个 Java 属性。对照真实工程 GuestBookSSM 的 WordMapper.xml(WordUser resultMap + association)。
resultType 自动映射:下划线 → 小驼峰(mapUnderscoreToCamelCase)
resultType:列名与属性名一致时自动映射,无需逐条声明(可开 mapUnderscoreToCamelCase 处理下划线↔驼峰);resultMap:显式声明每条 column→property,用于列名≠属性名、以及 association/collection 关联对象。
思考题
- MyBatis 为什么能用「接口方法 + XML SQL」的方式写 DAO?动态代理在其中起什么作用?
#{}和${}的区别是什么?为什么优先用#{}?<where>标签解决了手写动态 SQL 的什么问题?- resultType 与 resultMap 各适用什么场景?
<association>用来做什么? - 一次 Mapper 方法调用,从 call 到 map 中间经历了哪几步?
学习小测验
学习小测验
MyBatis 中 DAO 接口方法能直接执行 SQL,靠的是( )
#{} 占位符的特点是( )
"${}" 占位符适合用于( )
<where> 标签的作用是( )
<foreach> 标签常用于( )
resultType 与 resultMap 的区别是( )
resultMap 里的 <association> 用来( )
一次 Mapper 方法调用的正确顺序是( )