1.1 从JDBC到SQL映射手术台


文档摘要

1.1 从 JDBC 到 SQL 映射手术台 本节摘要:MyBatis 是一款半自动化 SQL 映射框架:SQL 由开发者书写,参数装配与结果组装由框架完成。本节用一段 JDBC 原生代码与等价的 MyBatis 写法逐行对照,数清框架替你消掉的重复劳动,并给出贯穿全册的"手术三段论"模型——参数入、动态拼装、结果出。 这是全册的起点:先看清要被替代的东西长什么样,后面每一章的机制才有着力点。 一台老式手术:JDBC 查询的六步 下面是一段再普通不过的原生 JDBC 代码,按用户名查一条用户记录。它来自大多数教材的第一课,也几乎原样出现在无数老项目的工具类里。 四十多行代码,与业务真正相关的只有两处:第 2 步的 SQL 文本,和第 3 步的参数值。

1.1 从 JDBC 到 SQL 映射手术台

本节摘要:MyBatis 是一款半自动化 SQL 映射框架:SQL 由开发者书写,参数装配与结果组装由框架完成。本节用一段 JDBC 原生代码与等价的 MyBatis 写法逐行对照,数清框架替你消掉的重复劳动,并给出贯穿全册的"手术三段论"模型——参数入、动态拼装、结果出。

这是全册的起点:先看清要被替代的东西长什么样,后面每一章的机制才有着力点。

一台老式手术:JDBC 查询的六步

下面是一段再普通不过的原生 JDBC 代码,按用户名查一条用户记录。它来自大多数教材的第一课,也几乎原样出现在无数老项目的工具类里。

// 原生 JDBC:按用户名查询一条用户 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 第 1 步:拿到连接(背后还有驱动注册、连接池等前置) conn = DriverManager.getConnection(url, user, password); // 第 2 步:预编译 SQL,参数用问号占位 ps = conn.prepareStatement("SELECT id, username, email FROM users WHERE username = ?"); // 第 3 步:手工装配参数,位置和类型都要自己盯 ps.setString(1, "zhangsan"); // 第 4 步:执行 rs = ps.executeQuery(); // 第 5 步:手工搬运结果集到对象 if (rs.next()) { User u = new User(); u.setId(rs.getInt("id")); u.setUsername(rs.getString("username")); u.setEmail(rs.getString("email")); System.out.println(u); // 输出:User{id=1, username='zhangsan', email='z@ex.com'} } } catch (SQLException e) { e.printStackTrace(); } finally { // 第 6 步:按依赖顺序反向关闭三个资源,一个都不能漏 try { if (rs != null) rs.close(); } catch (SQLException ignore) {} try { if (ps != null) ps.close(); } catch (SQLException ignore) {} try { if (conn != null) conn.close(); } catch (SQLException ignore) {} }

四十多行代码,与业务真正相关的只有两处:第 2 步的 SQL 文本,和第 3 步的参数值。其余全是"每个查询都要重抄一遍"的仪式性代码。更麻烦的是,这些仪式没有一处可以省略——顺序关错资源、参数位置填错、结果集列名拼错,都会变成运行期才爆的故障。

把这段代码的痛点列成清单,正好对应持久层框架要解决的四个问题:

JDBC 痛点 具体表现 MyBatis 的对策
资源管理冗长 每个查询六步,关闭顺序不能错 SqlSession 生命周期托管,try-with-resources 一行收尾
参数装配手工化 位置下标与类型全靠人肉对齐 井号占位符按名取值,TypeHandler 自动转换
结果搬运体力活 每列一次 getXxx 并 set 进对象 结果映射自动把列装进属性
SQL 混在 Java 里 改一句 SQL 要重新编译发布 SQL 集中在 XML 映射文件中维护

换到手术台上:等价的 MyBatis 写法

同样的查询,在 MyBatis 里被拆成两块。先是接口——注意,它没有实现类:

// Mapper 接口:只声明方法,不需要写实现 public interface UserMapper { // 方法名与 XML 中语句 id 一一对应 User findByUsername(String username); }

然后是 XML 映射文件,SQL 就住在这里:

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.mapper.UserMapper"> <!-- id 对应接口方法名,resultType 指明结果装进哪个类 --> <select id="findByUsername" parameterType="string" resultType="com.example.entity.User"> SELECT id, username, email FROM users WHERE username = #{username} </select> </mapper>

调用方只需要三行:

try (SqlSession session = factory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); User u = mapper.findByUsername("zhangsan"); // 控制台输出:User{id=1, username='zhangsan', email='z@ex.com'} System.out.println(u); }

对照着数:JDBC 六步里,第 1 步交给 openSession,第 3 步交给井号占位符加 TypeHandler,第 5 步交给 resultType 指向的自动映射,第 6 步交给 try-with-resources。剩下的只有 SQL 本身——这正是"半自动"的含义:框架不生成 SQL、不猜方言、不替你优化语句;它只负责把写好的 SQL 与 Java 方法缝合起来。

半自动的边界:MyBatis 不管什么

初学者常见的失望来自期望错位。下面三件事 MyBatis 不做,需要你自己负责:

  1. 不生成 SQL。没有"查全部"的默认方法,每个语句都要写。与之相对的全自动 ORM 会按对象关系生成 SQL,代价是复杂查询时要与生成逻辑搏斗。
  2. 不做数据库差异屏蔽。分页、日期函数、批量插入语法都随数据库走,切换数据库时映射文件要改。
  3. 不做对象状态跟踪。改了实体属性不会自动同步回数据库,必须显式调用更新方法。

这不是缺陷,是取舍。写 SQL 的主动权换来的是对每条语句的完全控制——这也是金融、报表、复杂查询密集的系统长期选择它的原因。反过来的场景,比如纯单表增删改查占九成的原型项目,全自动方案确实更快。

手术三段论:全册的地图

从本节开始,我们把每个映射方法都当成一台手术,拆成固定三段:

手术三段论:全册的地图

三段论不只是一个比喻,它也是排错的检查顺序:参数没进对(值错、名字对不上)、拼装不干净(语法错、条件漏)、结果没收好(字段 null、对象重复),几乎覆盖了 MyBatis 日常故障的全部。

变式练习:把本节的查询改成按邮箱查,JDBC 版与 MyBatis 版各写一遍,数一数你各写了几行、哪几行是抄来的。这个体感差异,就是后面所有章节要展开的空间。

本节要点回顾

  • 半自动定位:MyBatis 不生成 SQL、不屏蔽方言、不跟踪对象状态,它只负责 SQL 与方法之间的缝合
  • 四个替代点:资源管理、参数装配、结果搬运、SQL 隔离,分别对应 JDBC 六步里的四步仪式代码
  • 接口无实现:Mapper 接口只声明方法,运行期由动态代理生成实现(1.5 节拆解机制)
  • 手术三段论:参数入、动态拼装、结果出,既是全册结构,也是排错的检查顺序

下一节先把手术台搭起来:依赖、配置、第一条可运行的查询,以及首跑最容易撞上的三类报错。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U