当你涉及到两个其他业务方法一起绑定为一个业务操作的时候,例如一个ATM取款的简单操作:
ATM机器吐钱2和更新你帐户存款就是一个取钱的主要操作,它就需要要求如果两者都成功,即
一起提交,如果有一者失败,即回滚该两个操作,这种时候就需要事务了。
一、事务的特性:原子性、一致性、分离性、持久性,简称(ACID)。
原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
1、原子性:事务的原子性指的是,事务中包含的程序作为数据库的逻辑工作单位,它所做的对数据修改操作要么全部执行,要么完全不执行。这种特性称为原子性。
2、一致性:事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。这种特性称为事务的一致性。假如数据库的状态满足所有的完整性约束,就说该数据库是一致的。
3、分离性:分离性指并发的事务是相互隔离的。即一个事务内部的操作及正在操作的数据必须封锁起来,不被其它企图进行修改的事务看到。
4、持久性:持久性意味着当系统或介质发生故障时,确保已提交事务的更新不能丢失。即一旦一个事务提交,DBMS保证它对数据库中数据的改变应该是永久性的,耐得住任何系统故障。持久性通过数据库备份和恢复来保证。
二、为什么要用Spring来管理事务呢,有什么好处呢?
1、首先当然是工作量上的减轻,比传统要自己手工在程序写事务要减少很多工作,用spring只是需要简单的配置即可,灵活性也有提高,spring可以灵活的针对业务方法取消或者增加事务,也可以灵活配置事务的隔离级别等。
2、spring事务支持对于不同的事务api支持,比如jdbc,dbcp,hibernate等,可通过简单的配置支持JTA事务。
三、如何使用Spring来管理事务:
1、配置事务控制器:
- <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
- <property name="sessionFactory" ref="sessionFactory" />
- </bean>
2、AOP拦截器实现事务,统一配置了事务:
- <aop:config>
- <aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* com.cun.transaction.*Transaction.*(..))"/>
- </aop:config>
- <tx:advice id="txAdvice">
- <tx:attributes>
- <tx:method name="get*" read-only="true" />
- <tx:method name="query*" read-only="true" />
- <tx:method name="add*" propagation="REQUIRED" />
- <tx:method name="delete*" propagation="REQUIRED" />
- <tx:method name="update*" propagation="REQUIRED" />
- <tx:method name="add*" propagation="REQUIRED" />
- </tx:attributes>
- </tx:advice>
3、为单独一个业务方法配置事务,并指定为PROPAGATION_REQUIRES_NEW的事务:
- <bean id="potentialMemberProcessBussines" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
- <property name="transactionManager"><ref bean="transactionManager"/></property>
- <property name="target">
- <bean class="com.csair.cbd.member.services.business.PotentialMemberProcessBussines"/>
- </property>
- <property name="transactionAttributes">
- <props>
- <prop key="singelProcess">
- PROPAGATION_REQUIRES_NEW,ISOLATION_READ_COMMITTED,timeout_360,-Exception
- </prop>
- </props>
- </property>
- </bean>
PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
前六个策略类似于EJB CMT,第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。
它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager)
在我所见过的误解中, 最常见的是下面这种:
假如有两个业务接口 ServiceA 和 ServiceB, 其中 ServiceA 中有一个方法实现如下
- /**
- * 事务属性配置为 PROPAGATION_REQUIRED
- */
- void methodA() {
- // 调用 ServiceB 的方法
- ServiceB.methodB();
- }
这种想法可能害了不少人, 认为 Service 之间应该避免互相调用, 其实根本不用担心这点,PROPAGATION_REQUIRED 已经说得很明白,
如果当前线程中已经存在事务, 方法调用会加入此事务, 果当前没有事务,就新建一个事务, 所以 ServiceB#methodB() 的事务只要遵循最普通的规则配置为 PROPAGATION_REQUIRED 即可, 如果 ServiceB#methodB (我们称之为内部事务, 为下文打下基础) 抛了异常, 那么 ServiceA#methodA(我们称之为外部事务) 如果没有特殊配置此异常时事务提交 (即 +MyCheckedException的用法), 那么整个事务是一定要 rollback 的, 什么 Service 只能调 Dao 之类的言论纯属无稽之谈, spring 只负责配置了事务属性方法的拦截, 它怎么知道你这个方法是在 Service 还是 Dao 里 ?
最容易弄混淆的其实是 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED, 那么这两种方式又有何区别呢? 我简单的翻译一下 Juergen Hoeller 的话 :
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.
另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.
那么外部事务如何利用嵌套事务的 savepoint 特性呢, 我们用代码来说话
- ServiceA {
- /**
- * 事务属性配置为 PROPAGATION_REQUIRED
- */
- void methodA() {
- ServiceB.methodB(); }
- }
- ServiceB {
- /**
- * 事务属性配置为 PROPAGATION_REQUIRES_NEW
- */
- void methodB() {
- }
- }
这种情况下, 因为 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW, 所以两者不会发生任何关系, ServiceA#methodA 和 ServiceB#methodB 不会因为对方的执行情况而影响事务的结果, 因为它们根本就是两个事务, 在 ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了 (关于事务挂起的内容已经超出了本文的讨论范围, 有时间我会再写一些挂起的文章) .
- ServiceA {
- /**
- * 事务属性配置为 PROPAGATION_REQUIRED
- */
- void methodA() {
- ServiceB.methodB();
- }
- }
- ServiceB {
- /**
- * 事务属性配置为 PROPAGATION_NESTED
- */
- void methodB() {
- }
- }
ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(注意, 这是本文中第一次提到它, 嵌套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:
1. 改写 ServiceA 如下
- ServiceA {
- /**
- * 事务属性配置为 PROPAGATION_REQUIRED
- */
- void methodA() {
- try {
- ServiceB.methodB();
- } catch (SomeException) {
- // 执行其他业务, 如 ServiceC.methodC();
- }
- }
- }
这 种方式也是嵌套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点. (题外话 : 看到这种代码, 似乎似曾相识, 想起了 prototype.js 中的 Try 函数 )
2. 代码不做任何修改, 那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback (+MyCheckedException).
上面大致讲述了 嵌套事务的使用场景, 下面我们来看如何在 spring 中使用 PROPAGATION_NESTED, 首先来看 AbstractPlatformTransactionManager
JdbcTransactionObjectSupport 告诉我们必须要满足两个条件才能 createSavepoint :
2. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+
3. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0
4、当一个Service方法调用本Service类中的另一个方法时,内层事务不能正确地提交或者回滚。
确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了.