最近在公司做下单和支付功能,写到一个轮询第三方支付公司接口来查询订单支付状态的功能。

如果第三方返回支付成功,更新订单和支付数据,并做业务处理,然后将支付结果存redis。这里把支付结果放在redis里面是考虑到,前端页面查询支付结果并发量的问题。用户支付成功后会跳到我们系统里的支付结果页,这个页每秒查询一次后台,后台去查询第三方,那么查询的量会很大,加大数据库压力,也会提高查询第三方接口的数量。所以把支付数据缓存redis,将并发挡在外层,不直接查询数据。

除了轮询支付,同时也提供回调接口。然后这两个地方需要做幂等、并发控制和事务控制。如果多个请求同时过来,需要控制同一时间只能处理一次支付,这个采用redis分布式锁实现。然后具体的接口里先查询状态,如果订单已经支付成功,则直接返回,不做任何处理。如果返回第三方返回支付失败或者未支付,也不做任何处理;只处理支付成功的结果。如果更新订单、支付数据和业务处理失败,或抛出异常,则回滚数据库操作,保证数据一致性。

为保证事务一致性,将支付成功的代码放在一个事务里面,包括订单数据,支付数据和核心的业务数据(用户购买商品后的处理),而一些非核心的操作使用消息队列来做异步处理(创建收据和购买记录等)。这里也需要注意一点,就是消息队列一定要放在事务之外,订单和支付数据事务提交后才能发消息,否则就会发生消息消费后,但是这边事务还没提交,消息处理失败的情况。

这里记录一个问题,在测试环境调试的时候碰到的第一个问题:第一笔订单支付后,执行调度任务查询,然后处理时,因为查询业务bug,发生异常,但是看到订单和支付单状态数据却被更新成已支付。也就是业务逻辑处理失败,但是订单状态和支付状态没有回滚。

查看代码方法上已经添加了Transactional,可结果还是没有回滚。

@Transactional(rollbackFor = Exception.class)

于是回到本地做调试,并手写一个除数为0的异常,重现问题。之所以之前没有先在本地调试,而是去测试环境测,是因为第三方支付只能通过域名去调用。

重现步骤是这样的:

1、修改订单状态和支付单状态为待支付

2、复制测试环境的订单支付成功返回报文;

3、然后定一个报文字符串,用JSONObject将报文字符串转成对象,取代调用第三方接口;

4、修复业务逻辑的BUG,改为手写一个异常;

5、重试,查看订单和支付单状态;

6、发现没有回滚。

 

一、后来看了几遍代码,觉得查询和更新代码有问题,为什么呢?

查询OrderDTO和PayDTO在事务外,而更新OrderDTO和update PayDTO在事务方法内,修改代码重试。之所以这样写,考虑到事务的粒度,所以把查询放在方法外。

不管怎样,先试一试,修改代码,把查询和更新放在一起,都放在事务方法中。

结果:还是没回滚,所以跟这个没有关系;

二、接着排查:发现事务方法paySuccess里面更新OrderDTO和PayDTO都是事务方法,也就是事务方法嵌套事务方法,按理说spring事务默认的传播行为是Propagation.REQUIRED

也就是说有事务则加到当前事务中,没有事务,则创建一个新的事务,paySuccess是有事务的,按说里不会有问题,但是还是试一下。

修改代码,新增两个不带事务的PayService.updateWithoutTransaction和OrderService.updateWithoutTransaction,替换原本的update方法

结果:事务未回滚,所以也不是这个原因

三、上网查资料了,看到一遍spring事务失效的文章,链接:https://www.cnblogs.com/huangjinyong/p/14142662.html,一个类中不带事务的方法调用带事务的方法会存在事务不回滚的问题。

PayService.paySuccess和外层的PayService.queryUnPayOrderPayStatus都在一个类中,paySuccess带事务,而外层queryUnPayOrderPayStatus不带事务。不带事务的方法调用本类中带事务的方法,不会走aop代理类。

修改代码,将外层不带事务的方法PayService.queryUnPayOrderPayStatus复制到OrderService里面,由OrderService来查询订单支付状态,将paySuccess支付成功逻辑还保留在PayService里面。

重试,异常后事务回滚了。

原来这个aop代理还有一一层限制。记录一下。

代码入口:OrderController

 

循环处理:(不带事务,先查询所有未支付但是已经发起支付的订单,然后循环处理,并对单个处理进行try catch异常,保证一个支付单处理失败后,不影响for循环后面的支付单迭代处理):OrderService.queryUnPayOrderPayStatus

处理支付成功:带事务PayService.paySuccess,这里之前手写了一个除数为0的异常,调试完成后记得删除代码哦

 

结束。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐