大家用过mybatisPlus的都应该知道,好用是真的好用。不用自己写sql,不用像mybatis一样写#{}的占位符用于生成预编译sql语句,只要写eq,gt,lt等方法就可以...
等等,谁说mybatisPlus不要用占位符?今天遇到了一个场景,就害我掏出了占位符。不过也可能是因为我太菜了,如果大家有更好的想法也欢迎提出。
这个业务场景,就是秒杀下单时,实现一人一单功能。也就是说,同一个用户抢购某件商品只能抢购一次,再下单就要返回错误信息了。那么在高并发情况下,怎么保证同一个用户一秒内发起100次请求,只有一个请求能够更新商品库存成功(即下单成功)呢?
这牵扯到两张表——商品表和订单表。
下图是商品表的两个字段,一个代表商品id,一个代表商品库存
下图是订单表的三个字段,分别代表订单id,购买人用户id,购买的商品id
一般的思路是,先select查询订单表,判断是否有该用户对该商品的订单,如果没有订单,说明可以下单,再update更新商品表的stock库存。但是如果不加锁的话,这么做是不行的,查询和更新分开进行,容易导致高并发情况下的安全问题。
那么如何只用一条sql语句,实现一人一单呢?逻辑其实不难,拿到请求(其中存放了用户信息和购买商品id)后,更新库存时,用exists语句判断订单表中是否存在对应用户购买此商品的订单,如果存在就下单失败,如果此用户对此商品还一条下单信息都没有,才下单成功。
于是就想到了运用not exists语句,只用一条update完成一人一单。
boolean res = seckillVoucherService.update()
.setSql("stock = stock - 1") // set stock = stock - 1,代表库存-1
.eq("voucher_id", voucherId) //找到对应的商品来扣减库存
.gt("stock",0) // 库存大于0才能扣减
.notExists("select * from tb_voucher_order where user_id = " + userId + " and voucher_id = " + voucherId )
.update();
其中的notexists语句是这么写的:
.notExists("select * from tb_voucher_order where user_id = " + userId + " and voucher_id = " + voucherId)
等等,好像有点不对劲了,我怎么用拼接sql去了?。我在使用UpdateChainWrapper的notexists方法时,需要说明exists哪个userId和哪个voucherId,但不像where语句能用eq,exists语句好像并没有提供这样的语法糖,这个时候如果不用mybatisPlus的占位符的话,就只能拼接了,而拼接是不安全的。
终于进入正题了,那么mybatisPlus的占位符怎么写呢?是像mybatisPlus一样用#{}吗?还是像日志log.info一样,用{}占位呢?都不是。mybatis-plus的语法占位是这样的:
{索引}
{索引}是什么意思?其实索引就是参数的索引,比如我这条exists语句,用占位符前后对比:
不用占位符:.notExists("select * from tb_voucher_order where user_id = " + userId + " and voucher_id = " + voucherId)
用占位符:.notExists("select * from tb_voucher_order where user_id = {0} and voucher_id = {1}" ,userId,voucherId )
然后变量userId对应{0}的位置,voucherId对应{1}的位置,并在发送sql时以预编译形式发送:
UPDATE tb_seckill_voucher
SET stock = stock - 1
WHERE (voucher_id = ? AND stock > ? AND NOT EXISTS (select * from tb_voucher_order where user_id = ? and voucher_id = ?))
Parameters: 4(Long), 0(Integer), 1010(Long), 4(Long)
在另一篇文章中了解到,.where,.and中也可以这么用占位符,如下图所示。不过我个人感觉exists语句用到占位符会多一点。
public List<Entity> selectByNameAndIdCard(String name, String idCard) {
return super.selectList(new EntityWrapper<Entity>()
.where("name = {0}", name)
.and("idCard = {0}", idCard)
);
}
链接:
[JAVA] (框架) mybatis-plus 语法占位_mybatisplus 站位-CSDN博客
最后还有一个提醒:这个业务情境只是我学习过程中遇到的,所运用的并发查库策略不太好,实际情境下一定不要这样高并发查库,个人测试100请求/s查库可能导致死锁。本文主要是分享一下exists需要的mybatisPlus占位符的运用,如果大家觉得有不需要占位符的方法,欢迎在评论区讨论。