说起Spring的事务,笔者想起来刚开始接触java的时候,自己做的都是啥,举几个例子。
第一阶段:jsp+servlet进行学生管理系统的增删改查。具体一点就是新增一个学生,删除一个学生,修改一个学生的信息。
第二阶段:进行复杂一点的功能实现,比如分页展示等,初步尝试了list和map集合。
第三阶段:当实现选课功能时,突然意识到会存在超出人数能选择到的情况,使用了简单的事务控制,此时还是使用的Service代码里手动进行事务的开启和关闭。
第四阶段:接触到框架了,Spring,感叹接触的时间太晚了,这么方便,不过也正是因为自己以前手动实现了从0开始的一个过程,能够很快的理解Spring在帮我们做什么,特别是事务的地方,不必自己手动在每个方法开启,每个方法去关闭,AOP的优势就此体现。
对于Spring的事务,一直在用,但是一直没有总结,因此有必要总结一下,加深一下自己的理解。
1. Spring的配置文件:
<xsd:enumeration value="REQUIRED"/>
<xsd:enumeration value="SUPPORTS"/>
<xsd:enumeration value="MANDATORY"/>
<xsd:enumeration value="REQUIRES_NEW"/>
<xsd:enumeration value="NOT_SUPPORTED"/>
<xsd:enumeration value="NEVER"/>
<xsd:enumeration value="NESTED"/>
Spring的源码解释:
/**
* 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.
*/
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 J2EE).
* <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 J2EE).
* <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} else. 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
*/
int PROPAGATION_NESTED = 6;
PROPAGATION_REQUIRED
-- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED
总结一下:最常用的其实就是REQUIRED,这也是一般项目足够用了,要注意它的解释,支持当前事务,也即是说如果执行到某个方法是配置的此选项,那么它就会挂在当前事务下,如果之前都不存在任何事务,那么它新建。这也是我们常说的事务是传递的。
来看一个最简单的业务代码实现:
1.PROPAGATION_REQUIRED 一般在ServiceA中肯定会调用ServiceB的方法吧,那么此时methodA配置了事务,那么methodB失败了,会全部回滚掉,因为事务是传递的,上面解释的很清楚,笔者面试过一些人,他们认为service不应该调用serivce,而是应该调用dao层,很纳闷,为什么不能相互调用呢?
/*事务属性配置为 PROPAGATION_REQUIRED*/
ServiceA {
void methodA () {
// ServiceA调用 ServiceB 的方法
ServiceB.methodB();
}
}
ServiceB{
void methodB () {
serviceB的方法
}
}
2. PROPAGATION_REQUIRES_NEW
挑几个说一下,关于这个REQUIRED_NEW,具体来说,它的用途就是不管外部有没有事务,它都会
新起一个内部事务,它新起的这个内部事务和外部事务无关,这个内部事务是否成功失败,与外部事务没有任何关系。当内部事务结束时,外部事务才继续进行。
以下例子都是实际测试过,但是只给出简单的伪代码。
例子1:
同一个TestService中存在两个方法 testA 和testB,testA 配置REQUIRED,testB配置REQUIRED_NEW。
methodA(){
methodB();
throw new Exception("testA失败回滚");
}
methodB回滚了,受到了methodA的异常影响。
例子2:
同一个TestService中存在两个方法 testA 和testB,testA 配置REQUIRED,testB配置REQUIRED_NEW。
methodA(){
try{
...A
....A
methodB();
}catch(Exception e){
//1
}
}
methodB(){
...B
....B
throw new Exception("methodB失败回滚");
}
此例是methodB抛出异常回滚了,在methodA中的注释1能够catch住异常,但是即使你在此处抛出异常,methodA依然能够成功提交,也就是不受methodB的影响。
例子3:
不同Service中存在两个方法 testA 和testB,testA 配置REQUIRED,testB配置REQUIRED_NEW。
ServiceA.methodA(){
ServiceB.methodB();
throw new Exception("testA失败回滚");
}
此例中methodB是真正的不受methodA方法的影响,做到了新起事务。
总结例子1,例子2:
针对REQUIRED_NEW
如果同一个Service内两个方法的内层方法配置了REQUIRED_NEW,不会新起事务,只在当前事务下,所以任何异常都会造成一起回滚,但是如果在外层捕捉此异常,那么外层方法能够正常提交。
如果是不同Service中的两个方法,就会新起事务,相互不影响。
3. PROPAGATION_REQUIRES_NESTED
NESTED是真正的嵌套事务,它与外部事务息息相关,当内部事务开启时,会取得一个savepoint,如果嵌套事务失败,那么会回滚到此savepoint点,它属于外部事务的一部分,在外部事务结束时它才能被提交。
例子1:
<tx:method name="testA" propagation="REQUIRED" rollback-for="Throwable"/>
<tx:method name="testB" propagation="NESTED" rollback-for="Throwable"/>
<tx:method name="testC" propagation="REQUIRED" rollback-for="Throwable"/>
methodA(){
try{
...A
....A
methodB(); //1
....A //2
}catch(Exception e){
methodC();
}
}
methodB(){
...B
....B
throw new Exception("methodB失败回滚");
}
methodC(){
...C
....C
throw new Exception("methodC失败回滚");
}
这个应该是最常见的运用了,methodB采用的是NESTED配置,因此当抛出异常时,会被methodA捕捉,也就是回到savepoint点1,但是下面的2就不会执行了,但是能够执行methodC的,有点分支的意味。
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
因此,PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit, 这个规则同样适用于 roll back.
3. PROPAGATION_NOT_SUPPORTED
关于这个not_supported,是以非事务的方式运行,别忘了,一般项目配置时,会配置save,update,modify等开头的方法以事务方式运行,其他方法会以只读事务进行,但是通常这个只读事务会出现一点点问题。
此例是methodB抛出异常回滚了,在methodA中的注释1能够catch住异常,但是即使你在此处抛出异常,methodA依然能够成功提交,也就是不受methodB的影响。