以上篇文章为基础,本章讲解AT事务的使用。以火车抢票系统为例
1.GlobalTransactional注解的使用
GlobalTransactional要放在事务的发起方的Service层上的public方法上
在service的实现类上,加入注解,那么逻辑就是,用户在确认订单后,系统会在会员模块中给会员加入一条购票记录,如果中间确认订单出现问题,seata就是触发回滚将数据全部回滚。当前确认订单这个service就是事务的发起方,在business模块里。member模块里的只是参与方。
Seata 在发起方的 @GlobalTransactional 方法开始时创建全局事务 ID(XID),通过 Feign 请求头自动传给 member,member 的 Seata 数据源代理根据 XID 把自己加入全局事务。参与者不需要注解,Seata 自动识别。
java
@Service
public class AfterConfirmOrderServiceImpl implements AfterConfirmOrderService {
private static final Logger LOG = LoggerFactory.getLogger(AfterConfirmOrderServiceImpl.class);
@Resource
private DailyTrainSeatMapper dailyTrainSeatMapper;
@Resource
private DailyTrainTicketMapperCust dailyTrainTicketMapperCust;
@Resource
private MemberFeign memberFeign;
@Resource
private ConfirmOrderMapper confirmOrderMapper;
/**
* 选中座位后事务处理:
* 座位表修改售卖情况sell;
* 余票详情表修改余票;
* 为会员增加购票记录
* 更新确认订单为成功
*/
@GlobalTransactional
@Override
public void afterDoConfirm(DailyTrainTicket dailyTrainTicket, List<DailyTrainSeat> finalSeatList, List<ConfirmOrderTicketReq> tickets, ConfirmOrder confirmOrder) {
LOG.info("seata全局事务ID: {}", RootContext.getXID());
for (int j = 0; j < finalSeatList.size(); j++) {
DailyTrainSeat dailyTrainSeat = finalSeatList.get(j);
DailyTrainSeat seatForUpdate = new DailyTrainSeat();
seatForUpdate.setId(dailyTrainSeat.getId());
seatForUpdate.setSell(dailyTrainSeat.getSell());
seatForUpdate.setUpdateTime(new Date());
dailyTrainSeatMapper.updateByPrimaryKeySelective(seatForUpdate);
Integer startIndex = dailyTrainTicket.getStartIndex();
Integer endIndex = dailyTrainTicket.getEndIndex();
char[] chars = seatForUpdate.getSell().toCharArray();
Integer maxStartIndex = endIndex - 1;
Integer minEndIndex = startIndex + 1;
int minStartIndex = 0;
for (int i = startIndex - 1; i >= 0; i--) {
char aChar = chars[i];
if (aChar == '1') {
minStartIndex = i + 1;
break;
}
}
LOG.info("影响出发站区间:" + minStartIndex + "-" + maxStartIndex);
Integer maxEndIndex = seatForUpdate.getSell().length() + 1;
for (int i = endIndex; i < seatForUpdate.getSell().length(); i++) {
char aChar = chars[i];
if (aChar == '1') {
maxEndIndex = i + 1;
break;
}
}
LOG.info("影响到达站区间:" + minEndIndex + "-" + maxEndIndex);
dailyTrainTicketMapperCust.updateCountBySell(
dailyTrainSeat.getDate(),
dailyTrainSeat.getTrainCode(),
dailyTrainSeat.getSeatType(),
minStartIndex,
maxStartIndex,
minEndIndex,
maxEndIndex);
// 调用会员服务接口,为会员增加一张车票
MemberTicketReq memberTicketReq = new MemberTicketReq();
memberTicketReq.setMemberId(confirmOrder.getMemberId());
memberTicketReq.setPassengerId(tickets.get(j).getPassengerId());
memberTicketReq.setPassengerName(tickets.get(j).getPassengerName());
memberTicketReq.setTrainDate(dailyTrainTicket.getDate());
memberTicketReq.setTrainCode(dailyTrainTicket.getTrainCode());
memberTicketReq.setCarriageIndex(dailyTrainSeat.getCarriageIndex());
memberTicketReq.setSeatRow(dailyTrainSeat.getRow());
memberTicketReq.setSeatCol(dailyTrainSeat.getCol());
memberTicketReq.setStartStation(dailyTrainTicket.getStart());
memberTicketReq.setStartTime(dailyTrainTicket.getStartTime());
memberTicketReq.setEndStation(dailyTrainTicket.getEnd());
memberTicketReq.setEndTime(dailyTrainTicket.getEndTime());
memberTicketReq.setSeatType(dailyTrainSeat.getSeatType());
Result<Object> commonResp = memberFeign.save(memberTicketReq);
LOG.info("调用member接口,返回:{}", commonResp);
// 更新订单状态为成功
ConfirmOrder confirmOrderForUpdate = new ConfirmOrder();
confirmOrderForUpdate.setId(confirmOrder.getId());
confirmOrderForUpdate.setUpdateTime(new Date());
confirmOrderForUpdate.setStatus(ConfirmOrderStatusEnum.SUCCESS.getCode());
confirmOrderMapper.updateByPrimaryKeySelective(confirmOrderForUpdate);
}
}
}


在需要参与事务的模块的数据库中加入undo_log表,这是AT事务开启的关键!