项目背景
由于项目中使用了flowable流程引擎,然后自己在flowable基础上维护了一套自己的业务表数据。如果处理不当这样会引起 数据一致性问题。 即 我数据插入了流程引擎表,但是业务表插入失败了,二者数据不一致。如何处理? 下面详细分析一下如何处理这种情况。
问题描述
如何保证 流程引擎数据库 与 业务表数据一致性
解决方案
单机环境
大多数人看到这个问题的时候应该会想到可以通过事务来设置。
将事务管理器 transactionManager 设置进 flowable 的配置文件。
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration>{
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private DataSource dataSource;
@Override
public void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) {
springProcessEngineConfiguration.setActivityFontName("宋体");
springProcessEngineConfiguration.setLabelFontName("宋体");
springProcessEngineConfiguration.setAnnotationFontName("宋体");
springProcessEngineConfiguration.setAsyncExecutorActivate(false);
springProcessEngineConfiguration.setDatabaseType("mysql");
springProcessEngineConfiguration.setDataSource(dataSource);
// 这里设置 事务的传播属性
springProcessEngineConfiguration.setTransactionManager(transactionManager);
}
}
springboot2.x 启用事务
启动类上加上 @EnableTransactionManagement 注解
方法中加上 事务注解
@Transactional
常见事务失效场景
- 一个有@Transactional的方法被没有@Transactional方法调用时,会导致Transactional作用失效。也是最容易出现的情况。
由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。 - 对非public方法进行事务注解。@Transactional 将会失效。
由于在Spring AOP代理时,事务拦截器在目标方法前后进行拦截,DynamicAdvisedInterceptor的intercept 方法会获取Transactional注解的事务配置信息,
因为在Spring AOP 代理时,如上图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,这个方法会获取Transactional 注解的事务配置信息。他会首先校验事务方法的修饰符是不是public,不是 public则不会获取@Transactional 的属性配置信息。
3. Transactional 事务配置属性中的propagation 属性配置的问题
当propagation属性配置为:
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。 TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常.
事务传播属性 propagation
代表事务的传播行为,默认值为Propagation.REQUIRED。
Propagation.REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。比如A方法内部调用了B方法,此时B方法将会使用A方法的事务。
Propagation.MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
Propagation.NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
Propagation.REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。比如A方法使用默认的事务传播属性,B方法使用REQUIRES_NEW,此时A方法在内部调用B方法,一旦A方法出现异常,A方法中的事务回滚了,但是B方法并没有回滚,因为A和B方法使用的不是同一个事务,B方法新建了一个事务。
Propagation.NESTED:支持当前事务,新增Savepoint点,也就是在进入子事务之前,父事务建立一个回滚点,与当前事务同步提交或回滚。子事务是父事务的一部分,在父事务还未提交时,子事务一定没有提交。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
事务隔离级别 solation
事务的隔离级别,默认是Isolation.DEFAULT。
几种值的含义如下:
Isolation.DEFAULT:事务默认的隔离级别,使用数据库默认的隔离级别。
Isolation.READ_UNCOMMITTED:这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
Isolation.READ_COMMITTED:保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻读。
Isolation.REPEATABLE_READ:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读。
Isolation.SERIALIZABLE:这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。
分布式情况下
- 通过分布式事务 如 seata 等处理。
衍生问题: 当我们使用MQ与外部系统对接时,如何保证流程引擎与外部系统对接时的数据一致性。
衍生问题的解决方案:
分而治之,比别人多考虑一步,将流程引擎当成一个整体,操作成功后 再往MQ发送消息。
即在 流程引擎 与 MQ 之间加一层判断,当 流程引擎操作成功后再往MQ发送消息。
调整前:
调整后: