使用mybatis逆向工程的时候,delete方法的使用姿势不对,导致表被清空了,在生产上一刷新后发现表里没数据了,一股凉意从脚板心直冲天灵盖。
于是开发了一个拦截器,并写下这篇文章记录并分享。
这锅只能自己背了
你用过 mybatis 逆向工程(mybatis-generator-maven-plugin)生成相关文件吗?
就像这样式儿的:
可以看到逆向工程帮我们生成了实体类、Mapper 接口和 Mapper.xml。
用起来真的很方便,我用了好几年了,但是前段时间翻车了。
具体是怎么回事呢,我给大家摆一下。
先说一下需求吧。就是在做一次借据数据迁移的过程中,要先通过 A 服务的接口拿到所有的借据和对应的还款计划数据,然后再对这些借据进行核查,如果不满足某些添加,就需要从表中删除借据和对应的还款计划。
借据和对应的还款计划存放在两张表中,用借据号来关联。
而上线之后,我在一片欢声笑语中把还款计划表清空了,而这个必现的问题,在测试阶段同学还没有测试出来。
事情发生后我赶紧找到了 DBA 协助修复数据:
是怎么回事呢,为了模拟这个场景,我在本地创建了两张表,订单表(orderInfo)和订单扩展表(orderInfoExt),他们之间用订单号进行关联:
仅仅是做演示,所以两张表是非常简单的,
我们假设现在表里面的这条订单号为 2020060666666 的数据经过判断是错误数据,我当时写的代码体现在单元测试里面是这样的:
看出问题了吗?
第 42 行用的 example 对象还是 OrderInfo 的 example。而真正的 OrderInfoExt 对象的 exampleExt 对象没有进行任何赋值的操作。
为什么会出现这样的乌龙呢?
都怪 idea 太智能了!(强行找个借口)
我只需要打一个 ex 然后回个车.... example 就出现在代码里面了。
而这种没有参数的 example 传进去,在 mapper.xml 里面是这样处理的:
执行一下,看看效果:
看到 delete from order_info_ext 语句。你说你慌不慌?
当然在线上的服务器肯定是看不到执行的 SQL 的,但是当报警短信一条一条接着来的时候,当连上数据库一看表,发现数据没了的时候。
你说你慌不慌?
反正我一刷新后发现表里没数据了,一股凉意从脚板心直冲天灵盖。这种时候都还是要小小的心慌一下,先大喊一声“卧槽!数据怎么没了?”
然后赶紧报备,准备找 DBA 捞数据吧。
还好,本次误删不影响正常业务。
数据恢复过程就不说了,聊一下这事发生后我的一点思考吧。
哦,对了,还得说一下测试同学为什么没有发现这个问题。这个问题确实是一个必现的问题,测试案例上也写了这个测试点。
但是测试同学查看数据的时候用的是 select 语句,查询条件给的是确实需要被删除的数据 。
然后分别在两个表里面执行后发现:数据确实是没了。
是的,是数据确实是没了。整个表都干净了。
看着测试妹子惊慌失措的样子,我还能怎么说呢?
这锅,不甩了,我自己背下来吧。
重新审视逆向工程
我们先看看逆向工程帮我们生成的接口:
我相信用过 mybatis 逆向工程的朋友们,一看到这几个接口就知道了:哟,这都是老朋友了。
当我再去重新审视这些接口的时候我会发现其实还有会有一些问题的。
比如 delete 这样的高危语句我们还是需要尽量的手写 xml。
比如 updateByExample 同样存在由于误操作没有 where 条件,导致全表更新的情况。
比如 select 语句是查出了整个对象,但是有时间我们可能只需要对象里面的某个值而已。
比如 select 语句针对大表、关键表操作的时候,不能从代码的角度限定 SQL 必须带上索引字段查询。
上面的这些问题我们怎么处理呢?
我的建议是不要使用 mybatis 的逆向工程,全都手写。
开个玩笑。我们肯定不能因噎废食,何况逆向工程确实是帮我们做了很多工作,极大的方便我们这样的 CRUD Boy 进行 CRUD。
所以,我想 mybatis 的逆向工程肯定是有什么配置来控制生成哪些接口的,别问为什么,问就是直觉。
因为要是让我去开发这样的一个插件,我肯定也会提供对应的开关配置。
我现在的想法是不让它给我生成 delete 相关的接口,这个接口用起来我心里害怕。
所以怎么配置呢?
我们去它的 DTD 文件里面找一下嘛:
这个文件不长,一共也才 213 行,你能发现这一块东西:
你用脚指头想也能知道,这就是我们要找的开关配置。从 DTD 文件的描述中来看,这个几个参数是配置在 table 标签里面的。
我们去试一下:
果然是这样的。然后我们进行相关配置如下:
再生成一下:
果然,delete 相关的接口没了。
然后我们程序中真的需要 delete 操作的时候,再自己去手写 xml 文件。
那你自己写的 xml 文件也忘记写 where 条件了这么办?
这个月工资别领了。自己好好反思反思。
当然,就算你真的忘记写了,下面这个拦截器还能给你兜个底,帮你一把。
mybatis 拦截器使用
其实这个方案是我想到的第一个方案。导致上面问题的原因很简单嘛,就是执行了delete 语句却没有 where 条件。
那么我们可以拦截到这个 SQL 语句,然后对其进行两个判断:
是否是 delete 语句。 如果是,是否包含 where 条件。
那么问题来了,我们怎么去拦截到这个 SQL 呢?
答案就是我们可以开发一个 mybatis 插件呀,就像分页插件那样。
插件,听起来很高端的样子,其实他就是个拦截器。实现起来非常简单。
先去官网上看一下:
中文:https://mybatis.org/mybatis-3/zh/configuration.html#plugins
英文:https://mybatis.org/mybatis-3/configuration.html
在官网上,对于插件这一模块的描述是这样的:
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
正如官网说的这样,插件开发、使用起来是非常简单的。只需要三步:
1.实现 Interceptor 接口。
2.指定想要拦截的方法签名。
3.配置这个插件。