书接上文,我们刚刚被 MyBatis 的结果集映射上了一课。构造方法、resultType、resultMap 一顿组合拳下来,我已经深刻意识到一件事:MyBatis 这玩意,永远不会在你"以为已经会了"的地方出问题
既然查询已经把坑踩了一轮,那索性继续往前走,
这一篇我们来看 MyBatis 中真正绕不开的部分------DML 映射
也就是:插入、更新、删除
一、从一个最普通的 insert 说起
还是 user 表,结构不变。
插入接口我们这样定义:
java
int insert(UserInsertDTO dto);
对应的 XML:
xml
<insert id="insert">
insert into user(username, age, dept_id)
values (#{username}, #{age}, #{deptId})
</insert>
第一次看到这里的时候,我的内心活动大概是:
"就这?
不就是把参数塞进 SQL 里吗?"
从写法上看,确实没什么门槛,甚至可以说,这是 MyBatis 最"直观"的一部分。但也正因为太直观,很容易让人忽略它真正做的事情
二、MyBatis 是怎么"塞参数"的
#{} 并不是简单的字符串拼接,这一点在前面查数据的时候其实已经隐约见过了
当你调用 insert(dto) 时,MyBatis 会做几件事:
- 通过反射读取参数对象
- 根据属性名找到对应的 getter
- 使用 JDBC 的
PreparedStatement进行参数绑定
所以:
sql
values (#{username}, #{age}, #{deptId})
实际执行时并不是:
sql
values ('张三', 18, 1)
而更接近:
sql
values (?, ?, ?)
这一点很关键,它直接决定了后面所有行为的"底层规则":SQL 是预编译的,参数是按类型绑定的
这带来两个结果:
一是 SQL 注入基本和你无缘了;
二是 参数类型,必须和 Java 属性类型能对上。
很多插入时的"奇怪异常",其实都源于这里,只是当时你还意识不到
三、插入成功了,但事情还没结束
到这里为止,插入一般都不会翻车:接口调用成功,数据库里也能看到数据,看起来一切都很顺利
但问题往往不是"能不能插进去",而是插进去之后,你还想对这条数据做什么
比如:
- 我想拿到刚插入的 id
- 我想立刻查一遍验证结果
- 我想继续用这个对象做别的事
这些需求一出现,DML 映射的细节就开始显得重要了
四、插入后获取自增主键
MyBatis 提供了一种非常直观的方式:
xml
<insert id="insert"
useGeneratedKeys="true"
keyProperty="id">
insert into user(username, age, dept_id)
values (#{username}, #{age}, #{deptId})
</insert>
只要参数对象中存在 id 属性,插入完成后,MyBatis 会把数据库生成的主键值回填进去
这个设计本身没什么问题,但第一次用的时候,很容易产生一个误解:以为 Mapper 返回的 int 就是主键值
实际上并不是
Mapper 返回的 int,依然只是影响行数;
真正的主键,是被写回到了参数对象本身。
这一点如果没搞清楚,后面调试起来会非常拧巴。
五、update:真正的事故高发区
如果说 insert 是"看起来简单",
那 update 就是"看起来没问题,实际上问题很多"。
最容易写出来的 update 往往是这样:
xml
update user
set username = #{username},
age = #{age},
dept_id = #{deptId}
where id = #{id}
这段 SQL 本身没错,
但它默认了一个前提:你每次更新,都会把所有字段都传一遍
现实往往不是这样
有的字段不想改,有的字段就是 null,但你又不希望它被更新成 null
六、动态 SQL,才是 update 的正确打开方式
这时候,就轮到 MyBatis 的动态 SQL 出场了
xml
update user
<set>
<if test="username != null">
username = #{username},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="deptId != null">
dept_id = #{deptId},
</if>
</set>
where id = #{id}
这段 SQL 的价值在于:
- 只更新你明确传入的字段
- 自动处理多余的逗号
- 行为可预期,不靠"运气"
七、delete:越简单,越要小心
delete 在写法上反而最单纯:
xml
delete from user where id = #{id}
真正需要注意的只有一件事:where 条件一定要有
没有 where 的 delete,不是技术问题,是事故
八、为什么 DML 强烈推荐使用 DTO
写到这里,前面那次"构造方法翻车事件"就能彻底解释清楚了。
insert / update 关心的是:
- 哪些字段必填
- 哪些字段可选
- 参数从哪里来
而 select 关心的是:
- 结果如何映射
- 构造方式是什么
- 字段和属性如何对应
这两件事,本就不该由同一个类承担
所以生产中更常见的做法是:
- Entity:只负责结果映射
- DTO:专门负责 DML 参数
不是为了"优雅",是为了减少 MyBatis 的自由发挥空间
九、写到最后
这一篇没有什么"高深"的内容,但它决定了你后面写的每一条 SQL 是否安全、可控、好维护
如果说 resultMap 是在告诉 MyBatis:"结果你别乱猜,我来告诉你怎么映射";那 DTO + DML 映射就是在告诉它:"参数你只管用,别帮我造对象"
finish~