Spring事务传播行为实战

Spring事务传播行为实战

概述

Spring框架提供了事务管理的标准实现,且能够经过注解或者XML文件的方式声明和配置事务。前端

经过异步事件的方式解耦服务调用,能够提升程序的响应速度,而且避免由于事务传播行为而致使的事务问题。java

本文以一个电商平台包裹出库的业务为实际背景,经过异步事件与线程池的方式解耦嵌套事务,提升程序并发性能;为了便于问题的分析和方案的理解,同时还讲解了Spring的事务管理,并着重介绍了几种不一样的事务传播行为。spring

事务小贴士

什么是事务呢?简单来说事务就是逻辑上的一组操做,这些操做要么都执行,要么都不执行。数据库

什么是事务管理呢?所谓事务管理,其实就是“按照给定的事务规则来执行事务的提交或者回滚操做”。多线程

事务的机制实现很大一部分依赖事务日志文件(undo log和redo log)。事务日志是一个与数据库文件分开的文件。它存储对数据库进行的全部更改,并所有记录插入、更新、删除、提交、回退和数据库模式变化。事务日志是备份和恢复的重要组件。并发

订单出库失败

在订单的包裹出库以前,会将出库指令下发到商城,其中包含包裹寄送的快递信息,以便销售平台展现快递跟踪信息;同时,为了防止前端由于超卖或者重复下单等缘由,已经将订单取消,会根据前端商城的返回状态判断订单是否能够出库。框架

其中,系统交互流程以下,在前端销售平台与WMS(仓储管理系统)之间,会有统一的OMS(订单管理系统)进行销售单的管理和数据中转。异步

当前端销售平台收到出库信息之后,会进行以下的校验与操做:分布式

为了防止调用通知服务的时候出现异常,影响包裹出库,调用通知服务的代码是用try-catch语句包裹起来的。post

可是,某些订单在出库的时候,仍是出现了以下异常,出库失败:

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:873)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:710)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:534)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:305)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
复制代码

根据事务的传播行为,Transaction rolled back because it has been marked as rollback-only应该是由于被调用的事务回滚了,当调用一方提交事务的时候由于被标记为了rollback-only,所以没法正常提交。

Spring事务的传播机制

下面咱们详细聊一下Spring事务的传播机制。

Spring的@Transactional注解能够用于声明方法支持事务,Spring底层经过AOP的方式实现事务的控制。

@Transactional注解中的Propagation属性能够用于指定事务的传播行为。

/** * The transaction propagation type. * <p>Defaults to {@link Propagation#REQUIRED}. * @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior() */
Propagation propagation() default Propagation.REQUIRED;
复制代码

在TransactionDefinition定义中包括了以下几个表示传播行为的常量:

  • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 建立一个新的事务,若是当前存在事务,则把当前事务挂起。不支持当前事务。

  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,若是当前存在事务,则把当前事务挂起。不支持当前事务。

  • TransactionDefinition.PROPAGATION_NEVER:

以非事务方式运行,若是当前存在事务,则抛出异常。不支持当前事务。

  • TransactionDefinition.PROPAGATION_REQUIRED: 若是当前存在事务,则加入该事务;若是当前没有事务,则建立一个新的事务。支持当前事务。

  • TransactionDefinition.PROPAGATION_SUPPORTS: 若是当前存在事务,则加入该事务;若是当前没有事务,则以非事务的方式继续运行。支持当前事务。

  • TransactionDefinition.PROPAGATION_MANDATORY: 若是当前存在事务,则加入该事务;若是当前没有事务,则抛出异常。支持当前事务。

  • TransactionDefinition.PROPAGATION_NESTED: 若是当前存在事务,则建立一个事务做为当前事务的嵌套事务来运行;若是当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。支持当前事务。

这里须要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(若是存在外部事务的话),此时,内嵌事务并非一个独立的事务,它依赖于外部事务的存在,只有经过外部的事务提交,才能引发内部事务的提交,嵌套的子事务不能单独提交。若是熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中能够包括多个保存点,每个嵌套子事务。另外,外部事务的回滚也会致使嵌套子事务的回滚。

利用事务传播行为的解决方案

基于上面事务传播行为的讲解,能够将事务的传播行为设置为PROPAGATION_REQUIRES_NEW,这样当前事务与被调用事务将是两个不一样的事务,能够分别提交或者回滚。

在外层事务捕获异常之后执行以下代码,会输出false,说明内层事务回滚并未传播给外层事务: TransactionAspectSupport.currentTransactionStatus().isRollbackOnly()

而在内层事务执行以下代码,会返回true,说明内层事务是一个新的事务,在执行改事务的时候,当前事务会被挂起: TransactionAspectSupport.currentTransactionStatus().isNewTransaction()

这样就解决了try-catch块抛出异常因事务传播行为致使的当前事务没法提交的问题。

利用多线程的解决方案

咱们知道,在不一样的线程之间,事务是独立的。对于发送通知消息这样的业务,适合经过抛出事件的方式,而后经过异步监听器进行处理。

其流程以下:

至于事件模型的实现方式,不管是分布式的Zookeeper、Redis和MQ,仍是非分布式的Guava Event Bus、Spring Event均可以,能够根据实际须要选择。其核心原理仍然是发布订阅模式。

参考连接

数据库事务的方方面面

多是最漂亮的Spring事务管理详解

Transaction必知必会


Wechat-westcall
相关文章
相关标签/搜索