优惠券兑换码生成需求——事务同步回调问题分析

前段时间收到一个优惠券兑换码的需求:管理后台针对一个优惠券发起批量生成兑换码,这些兑换码可以导出分发到各个合作渠道(比如:抖音、京东等),用户通过这些渠道获取到兑换码之后,再登录到我司研发的商城,使用兑换码兑换获得对应的优惠券。

整个需求大致分为两个部分:(1)批量生成兑换码;(2)使用兑换码兑换优惠券。接下来的几篇文章将针对批量生成兑换码功能实现过程中碰到的一系列问题进行分析描述,以便读者再碰到类似问题,可以快速解决。

文章系列如下:

《事务失效问题分析》

《事务同步回调问题分析》

《批量生成任务全局限制唯一》

在此之前,先简单介绍商城技术架构:商城后端服务均采用SpringCloud框架开发,数据库主备,商城所有服务共用一个数据库,数据库持久化框架为MybatisPlus,所有服务采用K8s技术进行部署和治理。


一、问题描述

在《事务失效问题》一文末尾,笔者抛出了一个问题:兑换码生成记录初始状态是【生成中】,事务顺利提交,则该记录状态更新为【成功】。若执行异常导致事务回滚,则该记录状态需要更新为【失败】,该怎么处理?

有读者可能会问,为什么执行异常一定要更新状态为失败呢?不处理不行吗?这里先说说业务逻辑:整个平台需要保证同时只能有一个兑换码生成任务执行,因为兑换码的生成和批量插入比较占用资源。想想如果一次性要生成100w个兑换码并入库。为了实现这个要求,每个任务在check环节会校验是否有状态为【生成中】的任务记录,如果没有,则会插入一条状态为【生成中】的任务记录(5秒内禁止重复提交)。如果兑换码生成异常,事务回滚,没有将【生成中】状态更新为【失败】,则平台无法再次发起兑换码生成任务。

二、问题分析与解决

事务回滚之后进行回调处理涉及的技术方案是:事务同步回调处理逻辑。关键类:TransactionSynchronizationManager,事务同步管理器,监听Spring的事务操作。事务同步管理器通常使用方式如下:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void beforeCommit(boolean readOnly) {doSometing1();    //事务提交前处理逻辑}@Overridepublic void beforeCompletion() {doSometing2();    //事务完成前处理逻辑}@Overridepublic void afterCommit() {doSometing3();    //事务提交后处理逻辑}@Overridepublic void afterCompletion(int status) {doSometing4();    //事务完成后处理逻辑}
});

如果开发人员想在事务提交前、完成前、提交后和完成后,做一些额外的处理工作,则只需要覆盖重写上述4个方法。提交和完成的区别在于:无论commit还是rollback都是完成,TransactionSynchronization类中有三种完成状态定义:0表示事务提交,1表示事务回滚,2表示未知。

/** Completion status in case of proper commit. */
int STATUS_COMMITTED = 0;/** Completion status in case of proper rollback. */
int STATUS_ROLLED_BACK = 1;/** Completion status in case of heuristic mixed completion or system errors. */
int STATUS_UNKNOWN = 2;

现在我们回到兑换码生成功能中来,代码需要增加逻辑:如果事务回滚,则将兑换码生成记录状态更为失败。代码如下:

@Transactional(rollbackFor = Exception.class)
@Override
public void create(CodeCreateReqDTO codeCreateReqDTO) {StopWatch stopWatch = new StopWatch("兑换码生成");//生成兑换码并批量入库stopWatch.start("生成随机code");List<String> codeList = RedeemCodeUtils.generateRedeemCodes(codeCreateReqDTO.getNumber());stopWatch.stop();stopWatch.start("构建对象列表");List<DhCode> dhCodeList = codeList.stream().map(s -> {DhCode dhCode = new DhCode();...return dhCode;}).collect(Collectors.toList());stopWatch.stop();try {stopWatch.start("批量写入");if(!dhCodeService.saveBatch(dhCodeList)) throw new BusinessException(CommonConstants.FAIL, "批量保存兑换码失败!");stopWatch.stop();stopWatch.start("更新数量和状态");//更新优惠券已生成兑换码数量和未兑换的兑换码数量, 更新兑换码生成记录状态if(updateDhCodeNumberAndGenerateStatus(number, SUCCESS))) {log.info("优惠券生成兑换码成功!");} else {throw new BusinessException(CommonConstants.FAIL, "更新优惠券兑换码数量或兑换码记录状态失败!");}stopWatch.stop();} catch (Exception e) {log.warn("兑换码生成失败!", e);TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();    // 这里非常重要,否则注册回调无法生效}//注册事务同步回调TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {@Overridepublic void afterCompletion(int status) {if(TransactionSynchronization.STATUS_ROLLED_BACK == status){log.warn("========afterCompletion=========事务回滚============");updateGenerateStatus(FAIL);    //更新兑换码生成记录状态为失败}}});log.info(stopWatch.prettyPrint(TimeUnit.SECONDS));
}

上述代码中,有2个地方需要注意:

(1)第32-35行,这里必须catch所有异常,且不能再往外抛,且增加TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 只有这样子,注册事务同步回调机制才能生效;

(2)第38-46行,则是具体的事务回滚处理逻辑;

至此,文章开头抛出的问题可以顺利解决。那么细心的读者在看业务逻辑图时,可能带发现一个问题:插入兑换生成记录时如何保证并发安全?从下面的流程图来看,这里是存在并发问题,有可能会同时写入两条记录,那么就不能保证平台同时只有一个兑换码任务在执行了。请读者继续阅读《批量生成任务全局限制唯一》。

三、参考资料

  1. 兑换码生成工具类下载
  2. 《Spring进阶篇(7)-TransactionSynchronizationManager(事务监听)》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/615170.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿鹄云商B2B2C:JAVA实现的商家间直播带货商城系统概览

【saas云平台】打造全行业全渠道全场景的saas产品&#xff0c;为经营场景提供一体化解决方案&#xff1b;门店经营区域化、网店经营一体化&#xff0c;本地化、全方位、一站式服务&#xff0c;为多门店提供统一运营解决方案&#xff1b;提供丰富多样的营销玩法覆盖所有经营场景…

数据库与低代码:加速开发,提升效率的完美结合

随着技术的不断进步&#xff0c;数据库和低代码开发成为了现代应用程序开发中的两大关键要素。本文将探讨如何通过结合数据库和低代码开发&#xff0c;加速应用程序的开发过程&#xff0c;并提高开发效率和质量。 在过去的几十年中&#xff0c;数据库一直被视为应用程序开发中不…

使用srs_librtmp实现RTMP推流

1、背景 由于项目有需求在一个现有的产品上增加RTMP推流的功能&#xff0c;目前只推视频流。 2、方案选择 由于是在现有的产品上新增功能&#xff0c;那么为了减少总的成本&#xff0c;故选择只动应用软件的来实现需求。 现有的产品中的第三方库比较有限&#xff0c;连个ffmp…

Linux CentOS 7.6安装nginx详细保姆级教程

一、通过wget下载nginx压缩包 1、进入home文件并创建nginx文件夹用来存放nginx压缩包 cd /home //进入home文件夹 mkdir nginx //创建nginx文件夹 cd nginx //进入nginx文件夹2、下载nginx,我这里下载的是Nginx 1.24.0版本&#xff0c;如果要下载新版本可以去官网进行下载:…

回归预测 | Matlab基于SMA+WOA+SFO-LSSVM多输入单输出回归预测

回归预测 | Matlab基于SMAWOASFO-LSSVM多输入单输出回归预测 目录 回归预测 | Matlab基于SMAWOASFO-LSSVM多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 SMAWOASFO-LSSVM回归预测 基于黏菌算法鲸鱼算法向日葵算法优化LSSVM回归预测 其中包含三种改进…

RSIC-V“一芯”学习笔记(一)——概述

考研的文章和资料之后想写的时候再写怕趴 文章目录 一、阶段设计二、环境、开发语言和工具三、最重要的两个观念四、处理器芯片设计五、处理器芯片设计包含很多软件问题六、处理器芯片的评价指标七、复杂系统的构建和维护八、专业世界观九&#xff0c;提问的艺术(提问模板)十、…

Ubuntu下使用Virtual Box中显示没有可用的USB设备

Ubuntu中使用Virtual Box&#xff0c;但是使用到USB时只有USB1.1可以使用&#xff0c;并且提示没有可以使用的USB设备&#xff0c;解决方法如下 下载并安装Vitrual Box提供的功能扩展包 分别点击帮助->关于&#xff0c;查看当前使用的版本进入到Virtual Box官网下载链接根…

vue前端开发自学练习,Props数据传递-类型校验,默认值的设置!

vue前端开发自学练习,Props数据传递-类型校验,默认值的设置&#xff01; 实际上&#xff0c;vue开发框架的时候&#xff0c;充分考虑到了前端开发人员可能会遇到的各种各样的情况&#xff0c;比如大家经常遇到的&#xff0c;数据类型的校验&#xff0c;再比如&#xff0c;默认…

Spring之整合Mybatis底层源码

文章目录 一、整体核心思路1 . 简介2. 整合思路 二、源码分析1. 环境准备2. 源码分析 一、整体核心思路 1 . 简介 有很多框架需要与Spring进行整合&#xff0c;而整合的核心思路就是把其他框架所产生的对象放到Spring容器中&#xff0c;让其成为一个bean。比如Mybatis&#x…

在Colab上测试Mamba

我们在前面的文章介绍了研究人员推出了一种挑战Transformer的新架构Mamba 他们的研究表明&#xff0c;Mamba是一种状态空间模型(SSM)&#xff0c;在不同的模式(如语言、音频和时间序列)中表现出卓越的性能。为了说明这一点&#xff0c;研究人员使用Mamba-3B模型进行了语言建模…

Oladance、南卡、Cleer开放式耳机怎么样?全方位测评大PK!

​开放式耳机作为新兴的音频设备领域中备受欢迎的选择&#xff0c;但市场上琳琅满目的产品汇集了质量千差万别的耳机&#xff0c;其中存在着一些粗制滥造的产品。身为一位音频设备测评博主&#xff0c;我经常收到有关哪个品牌的开放式耳机质量好的疑问。面对市面上众多选择&…

MFC结合GDI+

MFC结合GDI 创建一个空的MFC界面&#xff0c;在确定按钮函数里进行画图&#xff1a; 1、包含头文件与库 在stdafx.h中加入以下三行代码&#xff1a; #include "gdiplus.h" using namespace Gdiplus; #pragma comment(lib, "gdiplus.lib")2、安装GDI 在…

uni-app做A-Z排序通讯录、索引列表

上图是效果图&#xff0c;三个问题 访问电话通讯录&#xff0c;拿数据拿到用户的联系人数组对象&#xff0c;之后根据A-Z排序根据字母索引快速搜索 首先说数据怎么拿 - 社区有指导https://ask.dcloud.net.cn/question/64117 uniapp 调取通讯录 // #ifdef APP-PLUSplus.contac…

安谋科技“周易”NPU与飞桨完成II级兼容性测试,助力实现多样化AI部署

近日&#xff0c;安谋科技&#xff08;中国&#xff09;有限公司&#xff08;以下简称“安谋科技”&#xff09;“周易”NPU系列IP与飞桨已完成II级兼容性测试&#xff0c;测试结果显示&#xff0c;双方兼容性表现良好&#xff0c;整体运行稳定。这是安谋科技加入“硬件生态共创…

【Node.js学习 day3——http模块】

创建HTTP服务端 //1.导入http模块 const http require(http);//2.创建服务对象 const server http.createServer((request, response) > {response.end(Hello HTTP Server);//设置响应体 });//3.监听端口&#xff0c;启动服务 server.listen(9000,()>{console.log(服务…

IMS中如何区分initial INVITE和re-INVITE?

这里就要先看下Dialog的定义。 dialog是两个UA之间持续一段时间的点对点 SIP关系。dialog通过SIP消息建立&#xff0c;例如对 INVITE request的 2xx response。dialog由Call-ID、local tag和remote tag来区分&#xff0c;也就是Call-ID 、from-tag和to-tag就可以确定一个dialog…

java锁的分类

锁定义和特征 乐观锁 VS 悲观锁 区别 乐观锁不会添加锁&#xff0c;无锁算法&#xff0c;没有线程被阻塞。悲观锁拿到资源就加锁&#xff0c;线程被阻塞。 乐观锁&#xff1a;CAS算法 Compare-And-Swap&#xff08;比较并交换&#xff09;的缩写,轻量级锁。 Java中&#xff…

数字档案安全与高效管理的先锋——亚信安慧AntDB数据库

档案工作在维护历史真实面貌、保障人民利益方面具有至关重要的作用。随着社会的发展&#xff0c;数字化转型成为档案管理领域的不可逆趋势。数字档案的存储和传输已经成为档案工作的重要组成部分&#xff0c;然而&#xff0c;这也伴随着一系列的挑战&#xff0c;其中安全风险是…

【MATLAB】逐次变分模态分解SVMD信号分解算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 1 基本定义 逐次变分模态分解&#xff08;Sequential Variational Mode Decomposition&#xff0c;简称SVMD&#xff09;是一种用于信号处理和数据分析的方法。它可以将复杂的信号分解为一系列模态函数&#xff0c;每个…