基于 TransactionInter... 的声明式事务管理
最初,Spring 提供了 TransactionInterceptor 类来实施声明式事务管理功能。先看清单8的配置文件:
清单 8. 基于 TransactionInterceptor 的事务管理示例配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
< beans... > ...... < bean
id = "transactionInterceptor" class = "org.springframework.transaction.interceptor.TransactionInterceptor" > < property
name = "transactionManager"
ref = "transactionManager" /> < property
name = "transactionAttributes" > < props > < prop
key = "transfer" >PROPAGATION_REQUIRED</ prop > </ props > </ property > </ bean > < bean
id = "bankServiceTarget" class = "footmark.spring.core.tx.declare.origin.BankServiceImpl" > < property
name = "bankDao"
ref = "bankDao" /> </ bean > < bean
id = "bankService" class = "org.springframework.aop.framework.ProxyFactoryBean" > < property
name = "target"
ref = "bankServiceTarget" /> < property
name = "interceptorNames" > < list > < idref
bean = "transactionInterceptor" /> </ list > </ property > </ bean > ...... </ beans > |
首先,我们配置了一个 TransactionInterceptor 来定义相关的事务规则,他有两个主要的属性:一个是 transactionManager,用来指定一个事务管理器,并将具体事务相关的操作委托给它;另一个是 Properties 类型的 transactionAttributes 属性,它主要用来定义事务规则,该属性的每一个键值对中,键指定的是方法名,方法名可以使用通配符,而值就表示相应方法的所应用的事务属性。
指定事务属性的取值有较复杂的规则,这在 Spring 中算得上是一件让人头疼的事。具体的书写规则如下:
1
|
传播行为
[,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常] |
- 传播行为是唯一必须设置的属性,其他都可以忽略,Spring为我们提供了合理的默认值。
- 传播行为的取值必须以“PROPAGATION_”开头,具体包括:PROPAGATION_MANDATORY、PROPAGATION_NESTED、PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED、PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_SUPPORTS,共七种取值。
- 隔离级别的取值必须以“ISOLATION_”开头,具体包括:ISOLATION_DEFAULT、ISOLATION_READ_COMMITTED、ISOLATION_READ_UNCOMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE,共五种取值。
- 如果事务是只读的,那么我们可以指定只读属性,使用“readOnly”指定。否则我们不需要设置该属性。
- 超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。
- 不影响提交的异常是指,即使事务中抛出了这些类型的异常,事务任然正常提交。必须在每一个异常的名字前面加上“+”。异常的名字可以是类名的一部分。比如“+RuntimeException”、“+tion”等等。
- 导致回滚的异常是指,当事务中抛出这些类型的异常时,事务将回滚。必须在每一个异常的名字前面加上“-”。异常的名字可以是类名的全部或者部分,比如“-RuntimeException”、“-tion”等等。
以下是两个示例:
1
2
3
4
|
< property
name = "*Service" > PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,TIMEOUT_20, +AbcException,+DefException,-HijException </ property > |
以上表达式表示,针对所有方法名以 Service 结尾的方法,使用 PROPAGATION_REQUIRED 事务传播行为,事务的隔离级别是 ISOLATION_READ_COMMITTED,超时时间为20秒,当事务抛出 AbcException 或者 DefException 类型的异常,则仍然提交,当抛出 HijException 类型的异常时必须回滚事务。这里没有指定"readOnly",表示事务不是只读的。
1
|
< property
name = "test" >PROPAGATION_REQUIRED,readOnly</ property > |
以上表达式表示,针对所有方法名为 test 的方法,使用 PROPAGATION_REQUIRED 事务传播行为,并且该事务是只读的。除此之外,其他的属性均使用默认值。比如,隔离级别和超时时间使用底层事务性资源的默认值,并且当发生未检查异常,则回滚事务,发生已检查异常则仍提交事务。
配置好了 TransactionInterceptor,我们还需要配置一个 ProxyFactoryBean 来组装 target 和advice。这也是典型的 Spring AOP 的做法。通过 ProxyFactoryBean 生成的代理类就是织入了事务管理逻辑后的目标类。至此,声明式事务管理就算是实现了。我们没有对业务代码进行任何操作,所有设置均在配置文件中完成,这就是声明式事务的最大优点。
基于 TransactionProxy... 的声明式事务管理
前面的声明式事务虽然好,但是却存在一个非常恼人的问题:配置文件太多。我们必须针对每一个目标对象配置一个 ProxyFactoryBean;另外,虽然可以通过父子 Bean 的方式来复用 TransactionInterceptor 的配置,但是实际的复用几率也不高;这样,加上目标对象本身,每一个业务类可能需要对应三个 <bean/> 配置,随着业务类的增多,配置文件将会变得越来越庞大,管理配置文件又成了问题。
为了缓解这个问题,Spring 为我们提供了 TransactionProxyFactoryBean,用于将TransactionInterceptor 和 ProxyFactoryBean 的配置合二为一。如清单9所示:
清单9. 基于 TransactionProxyFactoryBean 的事务管理示例配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
< beans...... > ...... < bean
id = "bankServiceTarget" class = "footmark.spring.core.tx.declare.classic.BankServiceImpl" > < property
name = "bankDao"
ref = "bankDao" /> </ bean > < bean
id = "bankService" class = "org.springframework.transaction.interceptor.TransactionProxyFactoryBean" > < property
name = "target"
ref = "bankServiceTarget" /> < property
name = "transactionManager"
ref = "transactionManager" /> < property
name = "transactionAttributes" > < props > < prop
key = "transfer" >PROPAGATION_REQUIRED</ prop > </ props > </ property > </ bean > ...... </ beans > |
如此一来,配置文件与先前相比简化了很多。我们把这种配置方式称为 Spring 经典的声明式事务管理。相信在早期使用 Spring 的开发人员对这种配置声明式事务的方式一定非常熟悉。
但是,显式为每一个业务类配置一个 TransactionProxyFactoryBean 的做法将使得代码显得过于刻板,为此我们可以使用自动创建代理的方式来将其简化,使用自动创建代理是纯 AOP 知识,请读者参考相关文档,不在此赘述。
基于 <tx> 命名空间的声明式事务管理
<!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启注解事物 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="false" />
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="create*" propagation="REQUIRED" rollback-for="java.lang.RuntimeException,java.lang.Exception" />
<tx:method name="modify*" propagation="REQUIRED" rollback-for="java.lang.RuntimeException,java.lang.Exception" />
<tx:method name="remove*" propagation="REQUIRED" rollback-for="java.lang.RuntimeException,java.lang.Exception" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 切面 -->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.tutu.mucar.core.biz.*.*.*(..))" />
</aop:config>
- <tx:advice>:事务通知定义,用于指定事务属性,其中“transaction-manager”属性指定事务管理器,并通过< tx:attributes >指定具体需要拦截的方法;
- <tx:method name="save*">:表示将拦截以save开头的方法,被拦截的方法将应用配置的事务属性:propagation="REQUIRED"表示传播行为是Required,isolation="READ_COMMITTED"表示隔离级别是提交读;
- <tx:method name="*">:表示将拦截其他所有方法,被拦截的方法将应用配置的事务属性:propagation="REQUIRED"表示传播行为是Required,isolation="READ_COMMITTED"表示隔离级别是提交读,read-only="true"表示事务只读;
- <aop:config>:AOP相关配置:
- <aop:pointcut/>:切入点定义,定义名为"serviceMethod"的aspectj切入点,切入点表达式为"execution(* cn..chapter9.service..*.*(..))"表示拦截cn包及子包下的chapter9. service包及子包下的任何类的任何方法;
- <aop:advisor>:Advisor定义,其中切入点为serviceMethod,通知为txAdvice。
从配置中可以看出,将对cn包及子包下的chapter9. service包及子包下的任何类的任何方法应用“txAdvice”通知指定的事务属性。
<tx:advice/>配置详解
声明式事务管理通过配置<tx:advice/>来定义事务属性,配置方式如下所示:
- <tx:advice id="……" transaction-manager="……">
- <tx:attributes>
- <tx:method name="……"
- propagation=" REQUIRED"
- isolation="READ_COMMITTED"
- timeout="-1"
- read-only="false"
- no-rollback-for=""
- rollback-for=""/>
- ……
- </tx:attributes>
- </tx:advice>
- <tx:advice>:id用于指定此通知的名字, transaction-manager用于指定事务管理器,默认的事务管理器名字为“transactionManager”;
- <tx:method>:用于定义事务属性即相关联的方法名;
name:定义与事务属性相关联的方法名,将对匹配的方法应用定义的事务属性,可以使用“*”通配符来匹配一组或所有方法,如“save*”将匹配以save开头的方法,而“*”将匹配所有方法;
propagation:事务传播行为定义,默认为“REQUIRED”,表示Required,其值可以通过TransactionDefinition的静态传播行为变量的“PROPAGATION_”后边部分指定,如“TransactionDefinition.PROPAGATION_REQUIRED”可以使用“REQUIRED”指定;
isolation:事务隔离级别定义;默认为“DEFAULT”,其值可以通过TransactionDefinition的静态隔离级别变量的“ISOLATION_”后边部分指定,如“TransactionDefinition. ISOLATION_DEFAULT”可以使用“DEFAULT”指定:
timeout:事务超时时间设置,单位为秒,默认-1,表示事务超时将依赖于底层事务系统;
read-only:事务只读设置,默认为false,表示不是只读;
rollback-for:需要触发回滚的异常定义,以“,”分割,默认任何RuntimeException 将导致事务回滚,而任何Checked Exception 将不导致事务回滚;异常名字定义和TransactionProxyFactoryBean中含义一样
no-rollback-for:不被触发进行回滚的 Exception(s);以“,”分割;异常名字定义和TransactionProxyFactoryBean中含义一样;
记不记得在配置方式中为了解决“自我调用”而导致的不能设置正确的事务属性问题,使用“((IUserService)AopContext.currentProxy()).otherTransactionMethod()”方式解决,在声明式事务要得到支持需要使用<aop:config expose-proxy="true">来开启。
基于 @Transactional 的声明式事务管理
除了基于命名空间的事务配置方式,Spring 2.x 还引入了基于 Annotation 的方式,具体主要涉及@Transactional 标注。@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如清单12所示:
清单12. 基于 @Transactional 的事务管理示例配置文件
1
2
3
4
|
@Transactional(propagation
= Propagation.REQUIRED) public
boolean transfer(Long fromId, Long toId, double amount) { return
bankDao.transfer(fromId, toId, amount); } |
Spring 使用 BeanPostProcessor 来处理 Bean 中的标注,因此我们需要在配置文件中作如下声明来激活该后处理 Bean,如清单13所示:
清单13. 启用后处理Bean的配置
1
|
< tx:annotation-driven
transaction-manager = "transactionManager" /> |
与前面相似,transaction-manager 属性的默认值是 transactionManager,如果事务管理器 Bean 的名字即为该值,则可以省略该属性。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
基于 <tx> 命名空间和基于 @Transactional 的事务声明方式各有优缺点。基于 <tx> 的方式,其优点是与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法,而基于 @Transactional 的方式必须在每一个需要使用事务的方法或者类上用 @Transactional 标注,尽管可能大多数事务的规则是一致的,但是对 @Transactional 而言,也无法重用,必须逐个指定。另一方面,基于 @Transactional 的方式使用起来非常简单明了,没有学习成本。开发人员可以根据需要,任选其中一种使用,甚至也可以根据需要混合使用这两种方式。
如果不是对遗留代码进行维护,则不建议再使用基于 TransactionInterceptor 以及基于TransactionProxyFactoryBean 的声明式事务管理方式,但是,学习这两种方式非常有利于对底层实现的理解。
虽然上面共列举了四种声明式事务管理方式,但是这样的划分只是为了便于理解,其实后台的实现方式是一样的,只是用户使用的方式不同而已。