当前位置:编程学堂 > 新颖的角度!第一次看到MyBatis这样使用,愣了一下,

新颖的角度!第一次看到MyBatis这样使用,愣了一下,

  • 发布:2023-10-05 09:05

大家好,我是伟伟。

本期给大家分享一下读者给我分享的一个关于MyBatis的“编程技巧”。说实话,这个炫酷的操作让我愣了一下。

我更喜欢称之为:埋雷骗你不商量的方法。

演示

为了让大家顺利进入乐趣,我先给大家演示一下。

因为我们需要使用MyBatis,所以我们首先创建两个表。

表称为产品表。表格结构非常简单:

还有一个表叫order_info表,表结构也很简单:

当你看到这两张桌子出现的时候,你就知道我的场景是什么了,肯定是卖货的。

库存减一,订单加一。

大家都再熟悉不过的场景了。

您可以在几分钟内编写如下伪代码:

public void saleProduct(){
//更新库存,库存减一
ProductMapper.updateProductCount();
//保存订单信息
OrderInfoMapper.saveOr德信息( );
}

当然,你一眼就能看出这段伪代码的问题:减少库存和节省订单应该是一个事务操作,所以这两个动作应该包装在一个事务中。

所以我们的伪代码变成这样:

public void saleProduct() {
//开启交易
begin;
//更新库存,库存减一
Boolean updateSuccess = ProductMapper.updateProductCount() ;
//保存订单信息
orderInfoMapper.saveOrderInfo();
                                  //提交交易
                                                                                    其他 {
                                                                                                                                                    回滚     

当时读者给我举个例子的时候,那是完全不同的场景,与卖货无关。

读者给出的例子大概是几个表之间存在关系。如果一张表中的某条数据被删除,其他几张表中对应的数据也会被删除,并且一张表需要更新其状态。

为了更好地展示这种“编程技巧”,我把场景简化为上面提到的卖货的方式。

我上面提到的是伪代码。

现在我将向您展示使用“编程技巧”编写的真实代码。

第一个是控制器接口:

@GetMapping("/sale")
public void sale() {
 productMapper.selaProduct(); }

然后就是这个productMapper的selaProduct接口:

是的,你没有看错,这是一个MyBatis的mapper接口,然后直接进入mapper.xml文件:

我什至不会问你这个写作方法或这个小技巧是否性感。我只想问你,你以前见过吗?

可以使用

吗?

伟师傅还太年轻,经验不够。我以前从未见过在mapper.xml 中这样写SQL。

别说我以前见过,在我小小的头脑里,我从来没想过要这样写。所以我看到这篇文章的第一反应是:这可能吗?这不行吗?

于是,秉着大胆假设、仔细验证的态度,我写了上面的Demo。

项目启动后,发起调用,控制台直接报错:

当我看到这个错误报告时,我下意识地觉得MyBatis不支持这种写法,直接报错。这也符合我之前的认知。

不过,在读者的指导下,他提醒我将这个配置添加到数据库连接配置中:

allowMultiQueries=true

我的demo启动时,确实没有添加这个配置。但当我看到这个配置的那一刻,我就开始觉得有点意思了。

因为我知道这个配置是用来做什么的。

看名字就知道意思:allow Multi Queries,允许多个查询。

最常用的场景是使用foreach标签进行批量插入或更新时使用此配置。

有了这个参数的支持,mapper.xml中写的sql很可能能够正常执行。

因为添加这个配置后,一个数据库连接中可以执行多条sql语句,但是对于MyBatis或MySQL驱动来说,它并不区分“多条sql”是insert语句还是update语句,还是混合体的句子。

我还去MySQL官网查了一下这个配置的含义:

https://www.sychzs.cn/doc/connector-j/8.1/en/connector-j-connp-props-security.html#cj-conn-prop_allowMultiQueries

这个参数,官网上只有一句话:

允许使用“;”在一个语句中分隔多个查询。此选项不会影响“addBatch()”和“executeBatch()”方法,它们依赖于“rewriteBatchStatements”。
允许使用“;”在一个语句中分隔多个查询。此选项不会影响“addBatch()”和“executeBatch()”方法,因为它们依赖于“rewriteBatchStatements”。

在引入allowMultiQueries的时候,还提到了一个rewriteBatchStatements参数。

至于这个参数的作用,这里就不多说了。我只能说这两件事是一套组合拳,里面的文章也很多。如果你不知道,我建议你去了解一下。

就当做课后练习吧。

我们先来说主线吧。

当我将配置allowMultiQueries=true添加到数据库连接时,我重新启动了服务。

再次发起呼叫。

为了表达我的震惊,我给你们做了一个动画:

库存减少一,订单增加一,方法执行成功。

确实有效。你做到了,真是令人大开眼界。

这波知识增加了,是一条从未想象过的路。

我的

不要这样写!

听韦大师的劝告,千万不要这样写!

首先,这种写法不符合大多数程序员的理解。

谁能想到最终的mapper.xml不只是简单的sql,里面还埋藏着一堆业务逻辑呢?

关键是这样写也有误导性。

举个简单的例子,这种写法没有考虑库存是否充足:

比如当前库存已经没有了,按照这种写法,仍然会向order_info表中插入一条数据。

超卖了,我的朋友。

只考虑commit,不考虑rollback。

而且这样写根本无法考虑超卖的情况,因为你无法得知减库存操作是否成功,从而无法判断是否需要commit或者rollback。

什么,你问我能不能写一个存储过程来判断?

是的,MyBatis 确实可以调用存储过程。

首先,存储过程还是要写在MySQL中,MyBatis只是发起调用。

其次,赶紧放弃你越走越远的顽皮想法,老老实实写Java代码来解决这个问题。是不是很好吃?

什么,你又问我,如果不需要判断前面的sql是否执行成功怎么办?

比如我前面提到的读者举的例子,几个表之间是有关系的。如果一个表中的某条数据被删除,那么其他表中相应的数据也必须被删除,而且还有一张表。需要更新状态。

大概是这样的:

开始;
从表1中删除where user_id=xxx;
从表2中删除whereuser_id=xxx;
从表3中删除其中 user_id=xxx;
更新表4 设置 user_status=1 其中 user_id=xxx;
提交;

与卖货的场景不同的是,在这个场景中,如果每条sql执行成功,就说明业务执行成功。

看起来没有什么问题。

但是我问你一个问题:这组SQL一定会被commit吗?

你想好了吗?

绝对不一定是这样。执行担保期间出现问题。

举个最简单的例子,表格写错了:

在这种情况下,再次发起呼叫:

程序报告错误,指出找不到表。

那么请问:此时订单表中是否应该有数据插入呢?

发生异常。绝对不应该插入任何数据。我检查了数据库,确实没有插入新数据。

看起来确实不错。

那我再问一下:这样写的话,当前事务是回滚了还是提交了?

。 。 。

。 。 。

。 。 。

正确答案暂停。

通过执行以下SQL,我们可以得到当前的事务列表:

从INFORMATION_SCHEMA.INNODB_TRX中选择*;

从查询结果中我们可以发现,我们的程序抛出异常后,当前事务仍然处于RUNNING状态:

而且,此事务将保持 RUNNING 状态,即挂起状态,直到服务重新启动。

但是仅仅从程序的角度来看,抛出了异常,没有数据,这也是符合预期的,没有什么问题。

是我的。

所以,听韦大师的建议,千万不要这样写!

老老实实写出大家都能理解的Java代码,不要乱搞mapper.xml。

延长

其实我觉得以上都是没什么用的知识点,因为人们一般不会这样写。

不过既然都写到这里了,场景也在那里,我也给大家拓展一些稍微有用的知识。

还是在卖货的背景下。

订单中加一,库存中减一。这就是它的工作原理。

开始;
插入 order_info(`buy_name`, `buy_goods`) VALUES ('歪大师', 'ipad pro 顶配版');
更新产品设置product_count=product_count-1其中id=1且product_count>0;
提交;

库存减一,订单增加一:

开始;
更新产品设置product_count=product_count-1其中id=1且product_count>0;
插入order_info(`购买名称`, `购买商品`) VALUES ('歪大师', 'ipad pro 顶配版');
提交;

都包含在交易中。为了简化代码,我们假设库存非常充足,不考虑回滚场景。

“订单加一、库存减一”的性能好还是“库存减一、订单加一”的性能好,还是两者没有区别?

首先从执行结果来看,两者确实没有什么区别,都能够保证业务场景的正确性。

但考虑性能的话,执行“订单加一,库存减一”肯定是更好的。

如果你还没弄清楚,我给你一个简单的提示:只要业务正确,锁定的代码与解锁的代码越接近,性能就越好?

如果你还没弄清楚,我再给你一个提示:当库存减一时,会被锁定吗?你并不关心它是否添加了表锁、间隙锁或记录锁。我就问你加不加锁?

如果你还没有反应过来,说明你对MySQL的锁机制的掌握有点薄弱,可以加强一下。

我直接公布答案:

更新产品集product_count=product_count-1,其中id=1且product_count>0;

因为where条件是id=1,加锁的是唯一索引,并且该记录存在于表中,所以只会锁定id=1的记录。

对于商品id=1,如果是热门商品,我们采用“订单加一,库存减一”的写法,性能会更高。

因为锁定频率相同时,解锁越快,性能越高。

看最后一张图你就明白了:

只要改一条SQL,性能就上去了。我问你,你觉得舒服吗?

最后说点不相关的:

我在文章开头给出了这样一张图:

你不觉得尴尬吗?

sela是什么鬼?

显然,这个地方是一个简单的拼写错误,你要输入的单词是sale:

请问,当你在程序中看到这样的拼写时,你会怎么做?

如果是我的话,我会主动把selaProduct改成saleProduct,其他的就不会动了。

这是我在上一篇文章《童子军规则》中提到的编码规则:

纠正拼写错误的方法名或变量名也是代码中非常重要的事情。

这不是代码痴迷,这是基本的职业道德。

因为你不想让下一个接手你代码的人看到一堆“succeess、createTiem、lastUpdataBy、businessDate、producectName”之类的变量名,而血压上升,非常生气并损害他们的健康。

相关文章