在大部分场景中,都会有同时操作数据库,并且需要操作中间件的场景,例如:操作数据库同时需要向redis、mq中保存数据或者发送消息,这时候就会涉及到,如果中间件报错影响数据入库怎么办?如果在操作中间件之后报错,数据库回滚,中间件的数据如何补偿?
这些问题,在正常的场景中不会有问题,但是在某些场景,会导致数据错误或者某个环节的功能无法工作,所以如何在事务中保证数据库和中间件操作的原子性呢
原因分析
-
例如以下伪代码
主要功能为:保存订单数据到数据库,并且向mq中发送消息,通知其他服务发送订单成功的短信,并保存订单的数据到redis中做数据统计和校验,并调用第三方服务进行数据传递
@Transactional public void orderSave(Order order, User user) { // 保存订单 orderDao.save(order); // 步骤1 // 更新订单用户表 userOrderDao.update(order, user); // 步骤2 // 通知短信服务发送短信 rabbitMqTemplate.sendMsg(order); // 步骤3 // 保存订单到redis中 redisTemplate.setCache("dev:user:order:success:" + user.getId(), order); // 步骤4 // 调用第三方接口,数据传递 thrityPartService.notify(order); // 步骤5 }
这种场景应该是一个较为常见的场景,有多种操作需要去进行做,并且在操作数据库时需要通过事务保证原子性和数据的一致性
-
异常场景
-
第一种:(正常)
若步骤1操作成功,步骤2报错,那么数据库回滚,后续的3、4、5都不会执行,保证了数据和程序的正确执行
-
第二种:(异常)
若步骤1、步骤2、步骤3、步骤4均成功,但是在步骤5报错,因为方法上有事务,所以会发生回滚,数据库的数据会被还原,但是步骤3、4都是操作中间件,无法和数据库在同一个原子性操作中,数据库的
@Transactional
事务注解无法控制操作中间件等第三方程序,所以数据库中数据回滚,但是redis
、mq
等无法回滚,会通知第三方服务,可能会导致数据不一致。
-
-
解决方案
-
第一种:
重构现有代码,将操作中间件的操作和调用第三方的代码(与数据库无关的代码)均移出事务中,在事务操作完成后的代码中执行其他的代码。
可能需要将多个无关的返回值进行封装,然后再事务方法外操作数据
-
第二种
使用Spring提供的事务的功能,依然可以保持在事务中调用方法,但是如果方法上有事务,那么则在事务提交之后才会调用该方法
@Transactional public void orderSave(Order order, User user) { // 保存订单 orderDao.save(order); // 步骤1 // 更新订单用户表 userOrderDao.update(order, user); // 步骤2 // 调用方法 orderOperateAfterTransaction(order, user); } /** * 如果没有事务则直接调用目标方法,如果有事务,则在事务提交之后调用目标方法 * */ public void orderOperateAfterTransaction(Order order, User user) { if (!TransactionSynchronizationManager.isSynchronizationActive()) { orderOperate(Order order, User user); } else { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { orderOperate(Order order, User user); } }); } } /** * 操作中间件的方法,保证数据库操作成功,事务提交之后再执行 */ public void orderOperate(Order order, User user) { // 通知短信服务发送短信 rabbitMqTemplate.sendMsg(order); // 保存订单到redis中 redisTemplate.setCache("dev:user:order:success:" + order.getId(), order); // 调用第三方接口,数据传递 thrityPartService.notify(order); }
不要再继续在事务方法中操作中间件了,会导致数据的不一致以及程序的执行问题,可以通过上述的方法来优化代码,保证程序的执行,并且减少大事务的产生,防止数据库连接占满,不仅提交效率,并且更好的优化代码~
-