本文只是测试用例,模拟订单测试场景,没有考虑高并发情况。 1.订单控制器
import cn.itcast.order.entity.Order;
import cn.itcast.order.service.OrderService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("order")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@PostMapping
public ResponseEntity<Long> createOrder(Order order){
Long orderId = orderService.create(order);
return ResponseEntity.status(HttpStatus.CREATED).body(orderId);
}
}
2.订单服务类
主事物开启,先开单在用户支付,发货扣减库存
@Override
@GlobalTransactional
public Long create(Order order) {
// 创建订单
orderMapper.insert(order);
try {
// 扣用户余额
accountTCCService.deduct(order.getUserId(), order.getMoney());
// 扣库存
storageClient.deduct(order.getCommodityCode(), order.getCount());
} catch (FeignException e) {
log.error("下单失败,原因:{}", e.contentUTF8(), e);
throw new RuntimeException(e.contentUTF8(), e);
}
return order.getId();
}
余额子事务
AccountTCCService 类
/**
* Tcc业务处理类
*/
@LocalTCC
public interface AccountTCCService {
/**
* 从用户账户中扣款
* Try接口
* name: 指定try接口
* commitMethod: confirm接口
* rollbackMethod:cancel接口
*/
@TwoPhaseBusinessAction(name = "deduct",commitMethod = "confirm",rollbackMethod = "cancel")
void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,@BusinessActionContextParameter(paramName = "money") int money);
/**
* Confirm接口
*/
public boolean confirm(BusinessActionContext cxt);
/**
* Cancel接口
*/
public boolean cancel(BusinessActionContext cxt);
业务具体实现
1.避免业务悬挂的问题,查询冻结金额表,是否存在记录,存在记录,且状态CANCEL,则证明执行过cancel,不执行try,没有问题记录冻结金额表以及事务状态
2.cancel 判断该事务是否已经执行过try方法 ,没有执行过try,执行空回滚,新增冻结金额数据,金额为0,状态为CANCEL
@Service
public class AccountTCCServiceImpl implements AccountTCCService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper;
@Override
@Transactional
public void deduct(String userId, int money) {
//获取seata内部产生的全局事务ID
String xid = RootContext.getXID();
//避免业务悬挂的问题
//查询冻结金额表,是否存在记录,存在记录,且状态CANCEL,则证明执行过cancel,不执行try
AccountFreeze oldFreeze = freezeMapper.selectById(xid);
if(oldFreeze!=null && oldFreeze.getState().equals(AccountFreeze.State.CANCEL)){
return;
}
//扣减account表可用余额
accountMapper.deduct(userId,money);
//记录冻结金额表以及事务状态
AccountFreeze freeze = new AccountFreeze();
freeze.setXid(xid);
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freezeMapper.insert(freeze);
}
@Override
public boolean confirm(BusinessActionContext cxt) {
int count = freezeMapper.deleteById(cxt.getXid());
return count==1;
}
@Override
public boolean cancel(BusinessActionContext cxt) {
//先查询account_freeze表记录
AccountFreeze freeze = freezeMapper.selectById(cxt.getXid());
//从BusinessActionContext取出之前存入的参数
String userId = (String)cxt.getActionContext("userId");
Integer money = (Integer)cxt.getActionContext("money");
//判断该事务是否已经执行过try方法
if(freeze==null){
//没有执行过try,执行空回滚
//新增冻结金额数据,金额为0,状态为CANCEL
freeze = new AccountFreeze();
freeze.setXid(cxt.getXid());
freeze.setUserId(userId);
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
freezeMapper.insert(freeze);
return true;
}
//修改account表,恢复可用金额
accountMapper.refund(userId,money);
//修改account_freeze表,冻结金额设置为0,stata为CANCEL
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
int count = freezeMapper.updateById(freeze);
return count==1;
}