一、事务特性
事务具有四个特点(ACID),如下:
1)原子性:要不全部成功,要不全部撤销
2)隔离性:事务之间相互独立,互不干扰
3)一致性:数据库正确的改变状态后,数据库的一致性约束没有被破坏
4)持久性:事务的提交结果,将持久保存在数据库中
二、Spring事务传播源码
Spring事务的传播特性定义在 TransactionDefinition 类中,源码如下:
package org.springframework.transaction;
import org.springframework.lang.Nullable;
/**
* Interface that defines Spring-compliant transaction properties.
* Based on the propagation behavior definitions analogous to EJB CMT attributes.
*
* <p>Note that isolation level and timeout settings will not get applied unless
* an actual new transaction gets started. As only {@link #PROPAGATION_REQUIRED},
* {@link #PROPAGATION_REQUIRES_NEW} and {@link #PROPAGATION_NESTED} can cause
* that, it usually doesn't make sense to specify those settings in other cases.
* Furthermore, be aware that not all transaction managers will support those
* advanced features and thus might throw corresponding exceptions when given
* non-default values.
*
* <p>The {@link #isReadOnly() read-only flag} applies to any transaction context,
* whether backed by an actual resource transaction or operating non-transactionally
* at the resource level. In the latter case, the flag will only apply to managed
* resources within the application, such as a Hibernate {@code Session}.
*
* @author Juergen Hoeller
* @see PlatformTransactionManager#getTransaction(TransactionDefinition)
* @see org.springframework.transaction.support.DefaultTransactionDefinition
* @see org.springframework.transaction.interceptor.TransactionAttribute
* @since 08.05.2003
*/
public interface TransactionDefinition {
/**
* Support a current transaction; create a new one if none exists.
* Analogous to the EJB transaction attribute of the same name.
* <p>This is typically the default setting of a transaction definition,
* and typically defines a transaction synchronization scope.
*/
// Spring默认的事务传播特性,支持当前事务,如果没有当前事务,则创建新事务运行
int PROPAGATION_REQUIRED = 0;
/**
* Support a current transaction; execute non-transactionally if none exists.
* Analogous to the EJB transaction attribute of the same name.
* <p><b>NOTE:</b> For transaction managers with transaction synchronization,
* {@code PROPAGATION_SUPPORTS} is slightly different from no transaction
* at all, as it defines a transaction scope that synchronization might apply to.
* As a consequence, the same resources (a JDBC {@code Connection}, a
* Hibernate {@code Session}, etc) will be shared for the entire specified
* scope. Note that the exact behavior depends on the actual synchronization
* configuration of the transaction manager!
* <p>In general, use {@code PROPAGATION_SUPPORTS} with care! In particular, do
* not rely on {@code PROPAGATION_REQUIRED} or {@code PROPAGATION_REQUIRES_NEW}
* <i>within</i> a {@code PROPAGATION_SUPPORTS} scope (which may lead to
* synchronization conflicts at runtime). If such nesting is unavoidable, make sure
* to configure your transaction manager appropriately (typically switching to
* "synchronization on actual transaction").
*
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
* @see org.springframework.transaction.support.AbstractPlatformTransactionManager#SYNCHRONIZATION_ON_ACTUAL_TRANSACTION
*/
// 支持当前事务,如果没有当前事务,则以非事务方式运行
int PROPAGATION_SUPPORTS = 1;
/**
* Support a current transaction; throw an exception if no current transaction
* exists. Analogous to the EJB transaction attribute of the same name.
* <p>Note that transaction synchronization within a {@code PROPAGATION_MANDATORY}
* scope will always be driven by the surrounding transaction.
*/
// 支持当前事务,如果当前没有事务则抛出异常
int PROPAGATION_MANDATORY = 2;
/**
* Create a new transaction, suspending the current transaction if one exists.
* Analogous to the EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code javax.transaction.TransactionManager} to be
* made available it to it (which is server-specific in standard Java EE).
* <p>A {@code PROPAGATION_REQUIRES_NEW} scope always defines its own
* transaction synchronizations. Existing synchronizations will be suspended
* and resumed appropriately.
*
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
// 新建事务,如果当前存在事务,则把当前事务挂起
int PROPAGATION_REQUIRES_NEW = 3;
/**
* Do not support a current transaction; rather always execute non-transactionally.
* Analogous to the EJB transaction attribute of the same name.
* <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box
* on all transaction managers. This in particular applies to
* {@link org.springframework.transaction.jta.JtaTransactionManager},
* which requires the {@code javax.transaction.TransactionManager} to be
* made available it to it (which is server-specific in standard Java EE).
* <p>Note that transaction synchronization is <i>not</i> available within a
* {@code PROPAGATION_NOT_SUPPORTED} scope. Existing synchronizations
* will be suspended and resumed appropriately.
*
* @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
*/
// 不支持当前事务,总是在非事务中运行,如果存在当前事务,就把当前事务挂起
int PROPAGATION_NOT_SUPPORTED = 4;
/**
* Do not support a current transaction; throw an exception if a current transaction
* exists. Analogous to the EJB transaction attribute of the same name.
* <p>Note that transaction synchronization is <i>not</i> available within a
* {@code PROPAGATION_NEVER} scope.
*/
// 不支持当前事务,如果存在当前事务,则抛出异常
int PROPAGATION_NEVER = 5;
/**
* Execute within a nested transaction if a current transaction exists,
* behave like {@link #PROPAGATION_REQUIRED} otherwise. There is no
* analogous feature in EJB.
* <p><b>NOTE:</b> Actual creation of a nested transaction will only work on
* specific transaction managers. Out of the box, this only applies to the JDBC
* {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}
* when working on a JDBC 3.0 driver. Some JTA providers might support
* nested transactions as well.
*
* @see org.springframework.jdbc.datasource.DataSourceTransactionManager
*/
// 如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则进行和 PROPAGATION_REQUIRED 类似的操作
int PROPAGATION_NESTED = 6;
}
从代码中可以看出Spring中共有 7 种事务传播属性:
1、PROPAGATION_REQUIRED:Spring默认的事务传播特性,方法被调用时自动开启事务,在事务范围内使用则使用同一个事务,否则开启新事务
2、PROPAGATION_SUPPORTS:自身不会开启事务,在事务范围内则使用相同事务,否则不使用事务(支持当前事务,如果当前没有事务,就以非事务方式执行)
3、PROPAGATION_MANDATORY:自身不开启事务,必须在事务环境使用否则抛出异常,在事务环境中使用同一个事务(支持当前事务,如果当前没有事务就会抛出异常)
4、PROPAGATION_REQUIRES_NEW:无论何时自身都会开启事务(新建事务,如果当前存在事务,把当前事务挂起)
5、PROPAGATION_NOT_SUPPORTED:自身不会开启事务,在事务范围内使用挂起事务,运行完毕事务恢复(不支持当前事务,以非事务方式执行,如果存在当前事务,就把当前事务挂起)
6、PROPAGATION_NEVER:自身不会开启事务,在事务范围使用抛出异常(以非事务方式执行操作,如果当前存在事务,则抛出异常)
7、PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则进行和 PROPAGATION_REQUIRED 类似的操作
注意:前六个事务传播属性类似于 EJB CMT,而第七个则是 JDBC 的(Spring 提供的一个特殊变量)
三、demo示例
下面以具体例子分析:
注意:首先应该清楚地是如果异常被捕获,则事务是不会回滚的,事务回滚默认的条件是遇到 RuntimeException,当然每个事务上可以自定义事务回滚的 Exception
3.1、PROPAGATION_REQUIRED(insertUser 传播属性是 REQUIRED)
3.1.1 insertGroup 传播属性是 REQUIRED 或者 SUPPORTS 或者 MANDATORY 或者 NESTED
或者
或者
或者
测试结果:无论 UserService 还是 GroupService 抛出异常,事务都将会回滚,说明二者使用的是同一个事务。
3.1.2 如果 GroupService 中 insertGroup 的事务传播属性是 NOT_SUPPORTED,具体代码如下:
3.1.3 insertGroup 的事务传播属性是 REQUIRES_NEW,具体代码如下:
1)insertUser 运行正常,insertGroup 抛出异常:
2)insertUser 抛出异常 ,insertGroup 运行正常 :
3.2、PROPAGATION_REQUIRES_NEW(insertUser 的传播属性是 REQUIRES_NEW )
3.2.1、insertGroup 传播属性是 REQUIRED 或者 MANDATORY 或者 SUPPORTS 或者 NESTED(无论二者谁抛出异常,事务都会回滚,由于二者使用的是同一个事务)
或者
或者
或者
3.2.2、insertGroup 传播属性是 REQUIRES_NEW
1)、insertUser 抛出异常,insertGroup 运行正常,则 insertUser 回滚,insertGroup 正常提交
2)、insertUser 运行正常,insertGroup 抛出异常,则 insertUser 回滚,insertGroup 回滚
3)、insertUser 运行正常,insertGroup 运行正常,则 insertUser 正常提交,insertGroup 正常提交
4)、insertUser 抛出异常,insertGroup 抛出异常,则 insertUser 回滚,insertGroup 回滚
3.2.3、insertGroup 传播属性是 REQUIRES_NOT_SUPPORTED
1)、insertUser 抛出异常,insertGroup 运行正常 或者 抛出异常,则 insertUser 回滚,insertGroup 正常提交
2)、insertUser 运行正常,insertGroup 运行正常 或者 抛出异常,则 insertUser 正常提交,insertGroup 正常提交
总结:insertUser 方法使用事务的传播属性 REQUIRED 或者 REQUIRES_NEW 时,无论 insertGroup 的事务传播是什么,只要在二者任一方法中出现抛出异常,insertUser 就会回滚
其实让我们最容易混淆的就是 REQUIRES_NEW 和 NESTED,那么这两种方式有什么区别呢?下面我们解析一下:
REQUIRES_NEW:启动一个新的,不依赖于环境的“内部”事务,这个事务将被完全的 commited 或者 rollback 而不依赖于外部事务,它拥有自己的隔离范围,自己的锁等等。当内部事务开始执行时,外部事务被挂起,内部事务结束时,外部事务将继续执行
NESTED:开始一个“嵌套的”事务,它是已经存在事务的一个真正的子事务,嵌套事务开始执行时,它将获取一个 savepoint。如果这个嵌套事务执行失败,我们将回滚到此 savepoint。嵌套事务是外部事务的一部分,只有外部事务结束后它才会提交。
由此可见,REQUIRES_NEW 和 NESTED 最大的区别在于:
REQUIRES_NEW 完全是一个新的事务,而 NESTED 则是外部事务的子事务,如果外部事务 commit ,嵌套事务也会被 commit,这个规则同样适用于 rollback