前言
最近将公司业务迁移到工作流框架上,在使用Flowable时遇到一个关于事务的问题,困扰了两天,便记录下来。
另 : Flowable的具体使用本文不进行赘述,等忙完这阵子整理一下!
问题发现
假设系统有这么一个流程实例

实例中,由两个 服务任务 组成,内部业务可能是调用第三方接口,也有可能处理自己的业务逻辑。
假设第三方接口通信不佳,在订单发起节点出现异常 :
1.节点内部没有try-catch的情况下,节点内部操作均会回滚。
2.流程不会执行到下一个节点,即配送环节。
假设配送环节内部业务异常:
1.节点内部没有try-catch的情况下,自身节点事务操作回滚,前面的订单发起节点操作也会回滚。
2.流程同样不会往下执行。
由此可见,流程实例启动后是一个大事务
期望 :
事务回滚是良性效果,但是希望异常节点后的节点不运行,可以直接到达结束节点!
参考资料
值得参考的好文章
作者的两篇文章描述很清楚
大事务引发的问题
上面的问题只是一个引子,解决如何终止流程。事务的问题才刚刚开始!
上文我得出结论 流程启动是一个大事务,我们先从源码中验证这一结论!
从一个流程实例的启动开始
runtimeService.startProcessInstanceByKeyAndTenantId
其内部实现,调用的是 CommandExecutorImpl 类的
public <T> T execute(CommandConfig config, Command<T> command) {
return this.first.execute(config, command);
}
this.first 是一个 CommandInterceptor 实现类,可以理解为是命令设计模式,
两个重要的实现类
SpringTransactionInterceptor
public <T> T execute(CommandConfig config, Command<T> command) {
LOGGER.debug("Running command with propagation {}", config.getTransactionPropagation());
int transactionPropagation = this.getPropagation(config);
if (transactionPropagation == 0 && TransactionSynchronizationManager.isActualTransactionActive()) {
return this.next.execute(config, command);
} else {
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
transactionTemplate.setPropagationBehavior(transactionPropagation);
return transactionTemplate.execute((status) -> {
return this.next.execute(config, command);
});
}
}
设置了事务的传播行为为REQUIRED
TransactionContextInterceptor
public <T> T execute(CommandConfig config, Command<T> command) {
CommandContext commandContext = Context.getCommandContext();
boolean openTransaction = !config.getTransactionPropagation().equals(TransactionPropagation.NOT_SUPPORTED) && this.transactionContextFactory != null && !commandContext.isReused();
boolean isContextSet = false;
Object var10;
try {
if (openTransaction) {
TransactionContext transactionContext = this.transactionContextFactory.openTransactionContext(commandContext);
Context.setTransactionContext(transactionContext);
isContextSet = true;
commandContext.addCloseListener(new TransactionCommandContextCloseListener(transactionContext));
}
var10 = this.next.execute(config, command);
} finally {
if (openTransaction && isContextSet) {
Context.removeTransactionContext();
}
}
return var10;
}
设置了流程开启到结束的事务开启。
关于Flow的事务源码,请查看网上大佬的文章
由此可见,流程的开启到结束是一个大事务
因为服务调用是在当前事务里,数据的产生或改变,在服务任务执行完之前,还没有提交到数据库.所以API对于数据库数据的操作,意味着未提交的操作在服务任务的API调用中都是不可见的
不用我提,大伙也会知道,大事务有诸多麻烦。
只要这个大事务不提交,节点的操作对其他事务都是不可见
简单的,如果我 订单发起节点 将订单状态置为Running,页面显示的仍然是非Runnning状态。
那么,我一个配送流程的节点很多,执行时间很长,肯定的,我需要拆分事务,分段提交。
事务分段提交后的一些问题
上文中,我们知道,流程开启的默认的事务传播行为是 REQUIRED,即如果事务存在,方法会加入当前事务,否则新起一个事务运行。显示,如果方法需要自己提交,那么就不能使用 REQUIRED,于是,在所有update操作方法上加上PROPAGATION_REQUIRES_NEW,即启用一个新的事务执行方法。
一些问题 :
update操作都加上新事务,流程节点的查询还是沿用之前的事务,我们知道
mysql默认是 可重复读, 事务启动时,视图可以认为是静态,不受其他事务更新的影响。
也就是说,如果需要配送两个节点,第一个节点配送完成,更改状态为Finishi,继续第二个节点,此时查询仍然是Finishi的这条数据,陷入无限循环,出现脏读。
因为查询的事务是在流程开启的时候就启动了,update事务不会影响到它,显然,我们要的结果是需要影响到它的读。
显然,查询事务也需要开启新事务去读取。
博客围绕系统流程实例中的事务问题展开。先指出第三方接口通信不佳或内部业务异常时,事务回滚会导致流程无法继续。接着说明流程开启到结束是大事务,存在诸多麻烦,需拆分事务分段提交。但分段提交后又出现脏读问题,查询事务也需开启新事务读取。
1964

被折叠的 条评论
为什么被折叠?



