【MyBatis】DML映射

书接上文,我们刚刚被 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~

相关推荐
PPPPickup5 分钟前
easymall---管理后端商品属性管理
java
人道领域9 分钟前
SSM框架从入门到入土(SpringFrameWork)
java·spring boot·tomcat
源力祁老师22 分钟前
深入解析 Odoo 中 default_get 方法的功能
java·服务器·前端
团子的二进制世界22 分钟前
Sentinel-服务保护(限流、熔断降级)
java·开发语言·sentinel·异常处理
NWU_白杨23 分钟前
多线程安全与通信问题
java
sheji341628 分钟前
【开题答辩全过程】以 工业车辆维修APP设计与实现为例,包含答辩的问题和答案
java
虫小宝35 分钟前
淘客系统的容灾演练与恢复:Java Chaos Monkey模拟节点故障下的服务降级与快速切换实践
java·开发语言
yxm263366908137 分钟前
【洛谷压缩技术续集题解】
java·开发语言·算法
键盘帽子38 分钟前
多线程情况下长连接中的session并发问题
java·开发语言·spring boot·spring·spring cloud
无名-CODING1 小时前
Spring事务管理完全指南:从零到精通(上)
java·数据库·spring