前提说明
在电商项目中,下订单时通常会涉及多个表操作,比如商品表、订单表和订单状态表等。为了保证下订单这一操作的原子性,所以下单时需要保证这些表的操作要么同时成功要么同时失败。通常情况下我们都会在下订单方法中加上事务处理,常用的都是借助Spring来进行事务管理。
同时下单时需要进行检查库存,扣减库存这些操作。为了防止超卖问题,通常我们会在这些操作加上同步锁来保证原子操作。
本次我采用了Spring声明式事务@Transaction
和synchronized
标注的方法,发现在单体的架构下还是出现了商品超卖的问题。下面我们一起来模拟这个出现超卖的流程。
模拟超卖流程
1. 数据库准备
数据库准备三张表,分别时order、order_item和product。往商品表中插入一条商品的信息,设置库存为1。
# 商品表
CREATE TABLE `product` (
`id` int(11) NOT NULL,
`product_name` varchar(255) NOT NULL,
`count` int(5) NOT NULL,
`create_time` time NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
# 订单信息表
CREATE TABLE `order_item` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`create_time` time NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
# 订单状态表
CREATE TABLE `order_status` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_status` int(1) NOT NULL,
`create_time` time NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
2. 测试程序
service层
@Service
@Slf4j
public class OrderService {
/**
* 模拟下订单方法
* @param productId
* @param productNum
* @return
* @throws Exception
*/
@Transactional(rollbackFor = Exception.class)
public synchronized Integer createOrder(Integer productId, Integer productNum) throws Exception {