Day4-微服务-Seata默认事务

以上篇文章为基础,本章讲解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事务开启的关键!

相关推荐
我科绝伦(Huanhuan Zhou)8 分钟前
MySQL极端场景数据丢失防护:从Crash-Safe到异地备份
数据库·mysql
辞旧 lekkk9 分钟前
【Redis初阶】常见数据类型
开发语言·数据库·c++·redis·学习·缓存·bootstrap
帅次41 分钟前
Kotlin 与 Java 互操作:混合工程里的平台类型与 API 边界
java·开发语言·kotlin·suspend·nullable
明志数科1 小时前
人形机器人格斗场景下的技术挑战:动力学控制、感知融合与实时决策
网络·数据库
酉鬼女又兒1 小时前
零基础入门 DeepSeek V4 Pro API 开发:从环境搭建、消息格式规范到翻译函数实战、少样本提示、多轮对话聊天机器人与常见报错全流程详解指南
大数据·网络·数据库·人工智能·macos·机器人·github
笨蛋不要掉眼泪1 小时前
MySQL架构揭秘:MVCC解决读一致性问题
数据库·mysql·架构
X-⃢_⃢-X1 小时前
一、第一阶段:认识 Spring Boot
java·spring boot·后端
前端工作日常1 小时前
我学习到的Java程序的生命周期
java·后端
Navicat中国2 小时前
开发者入门:无服务器数据库
数据库·云原生·serverless·navicat
SunnyDays10112 小时前
Java 实现 PDF 页面删除、排序、旋转和裁剪
java·pdf