Spring 申明式事务详解

这是从IBMdeveloperWork上的一篇文章。原文:

[url]http://www.ibm.com/developerworks/cn/education/opensource/os-cn-spring-trans/section5.html

[/url]

声明式事务管理

Spring的声明式事务管理概述

Spring的声明式事务管理在底层是建立在AOP的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正是AOP的用武之地。Spring开发团队也意识到了这一点,为声明式事务提供了简单而强大的支持。

声明式事务管理曾经是EJB引以为傲的一个亮点,如今Spring让POJO在事务管理方面也拥有了和EJB一样的待遇,让开发人员在EJB容器之外也用上了强大的声明式事务管理功能,这主要得益于Spring依赖注入容器和SpringAOP的支持。依赖注入容器为声明式事务管理提供了基础设施,使得Bean对于Spring框架而言是可管理的;而SpringAOP则是声明式事务管理的直接实现者,这一点通过清单8可以看出来。

通常情况下,笔者强烈建议在开发中使用声明式事务,不仅因为其简单,更主要是因为这样使得纯业务代码不被污染,极大方便后期的代码维护。

和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

下面就来看看Spring为我们提供的声明式事务管理功能。

基于TransactionInter...的声明式事务管理

最初,Spring提供了TransactionInterceptor类来实施声明式事务管理功能。先看清单8的配置文件:

清单8.基于TransactionInterceptor的事务管理示例配置文件

<beans...>

......

<beanid="transactionInterceptor"

class="org.springframework.transaction.interceptor.TransactionInterceptor">

<propertyname="transactionManager"ref="transactionManager"/>

<propertyname="transactionAttributes">

<props>

<propkey="transfer">PROPAGATION_REQUIRED</prop>

</props>

</property>

</bean>

<beanid="bankServiceTarget"

class="footmark.spring.core.tx.declare.origin.BankServiceImpl">

<propertyname="bankDao"ref="bankDao"/>

</bean>

<beanid="bankService"

class="org.springframework.aop.framework.ProxyFactoryBean">

<propertyname="target"ref="bankServiceTarget"/>

<propertyname="interceptorNames">

<list>

<idrefbean="transactionInterceptor"/>

</list>

</property>

</bean>

......

</beans>

首先,我们配置了一个TransactionInterceptor来定义相关的事务规则,他有两个主要的属性:一个是transactionManager,用来指定一个事务管理器,并将具体事务相关的操作委托给它;另一个是Properties类型的transactionAttributes属性,它主要用来定义事务规则,该属性的每一个键值对中,键指定的是方法名,方法名可以使用通配符,而值就表示相应方法的所应用的事务属性。

指定事务属性的取值有较复杂的规则,这在Spring中算得上是一件让人头疼的事。具体的书写规则如下:

传播行为[,隔离级别][,只读属性][,超时属性][不影响提交的异常][,导致回滚的异常]

*传播行为是唯一必须设置的属性,其他都可以忽略,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”等等。

以下是两个示例:

<propertyname="*Service">

PROPAGATION_REQUIRED,ISOLATION_READ_COMMITTED,TIMEOUT_20,

+AbcException,+DefException,-HijException

</property>

以上表达式表示,针对所有方法名以Service结尾的方法,使用PROPAGATION_REQUIRED事务传播行为,事务的隔离级别是ISOLATION_READ_COMMITTED,超时时间为20秒,当事务抛出AbcException或者DefException类型的异常,则仍然提交,当抛出HijException类型的异常时必须回滚事务。这里没有指定"readOnly",表示事务不是只读的。

<propertyname="test">PROPAGATION_REQUIRED,readOnly</property>

以上表达式表示,针对所有方法名为test的方法,使用PROPAGATION_REQUIRED事务传播行为,并且该事务是只读的。除此之外,其他的属性均使用默认值。比如,隔离级别和超时时间使用底层事务性资源的默认值,并且当发生未检查异常,则回滚事务,发生已检查异常则仍提交事务。

配置好了TransactionInterceptor,我们还需要配置一个ProxyFactoryBean来组装target和advice。这也是典型的SpringAOP的做法。通过ProxyFactoryBean生成的代理类就是织入了事务管理逻辑后的目标类。至此,声明式事务管理就算是实现了。我们没有对业务代码进行任何操作,所有设置均在配置文件中完成,这就是声明式事务的最大优点。

基于TransactionProxy...的声明式事务管理

前面的声明式事务虽然好,但是却存在一个非常恼人的问题:配置文件太多。我们必须针对每一个目标对象配置一个ProxyFactoryBean;另外,虽然可以通过父子Bean的方式来复用TransactionInterceptor的配置,但是实际的复用几率也不高;这样,加上目标对象本身,每一个业务类可能需要对应三个<bean/>配置,随着业务类的增多,配置文件将会变得越来越庞大,管理配置文件又成了问题。

为了缓解这个问题,Spring为我们提供了TransactionProxyFactoryBean,用于将TransactionInterceptor和ProxyFactoryBean的配置合二为一。如清单9所示:

清单9.基于TransactionProxyFactoryBean的事务管理示例配置文件

<beans......>

......

<beanid="bankServiceTarget"

class="footmark.spring.core.tx.declare.classic.BankServiceImpl">

<propertyname="bankDao"ref="bankDao"/>

</bean>

<beanid="bankService"

class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">

<propertyname="target"ref="bankServiceTarget"/>

<propertyname="transactionManager"ref="transactionManager"/>

<propertyname="transactionAttributes">

<props>

<propkey="transfer">PROPAGATION_REQUIRED</prop>

</props>

</property>

</bean>

......

</beans>

如此一来,配置文件与先前相比简化了很多。我们把这种配置方式称为Spring经典的声明式事务管理。相信在早期使用Spring的开发人员对这种配置声明式事务的方式一定非常熟悉。

但是,显式为每一个业务类配置一个TransactionProxyFactoryBean的做法将使得代码显得过于刻板,为此我们可以使用自动创建代理的方式来将其简化,使用自动创建代理是纯AOP知识,请读者参考相关文档,不在此赘述。

基于<tx>命名空间的声明式事务管理

前面两种声明式事务配置方式奠定了Spring声明式事务管理的基石。在此基础上,Spring2.x引入了<tx>命名空间,结合使用<aop>命名空间,带给开发人员配置声明式事务的全新体验,配置变得更加简单和灵活。另外,得益于<aop>命名空间的切点表达式支持,声明式事务也变得更加强大。

如清单10所示:

清单10.基于<tx>的事务管理示例配置文件

<beans......>

......

<beanid="bankService"

class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">

<propertyname="bankDao"ref="bankDao"/>

</bean>

<tx:adviceid="bankAdvice"transaction-manager="transactionManager">

<tx:attributes>

<tx:methodname="transfer"propagation="REQUIRED"/>

</tx:attributes>

</tx:advice>

<aop:config>

<aop:pointcutid="bankPointcut"expression="execution(**.transfer(..))"/>

<aop:advisoradvice-ref="bankAdvice"pointcut-ref="bankPointcut"/>

</aop:config>

......

</beans>

如果默认的事务属性就能满足要求,那么代码简化为如清单11所示:

清单11.简化后的基于<tx>的事务管理示例配置文件

<beans......>

......

<beanid="bankService"

class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">

<propertyname="bankDao"ref="bankDao"/>

</bean>

<tx:adviceid="bankAdvice"transaction-manager="transactionManager">

<aop:config>

<aop:pointcutid="bankPointcut"expression="execution(**.transfer(..))"/>

<aop:advisoradvice-ref="bankAdvice"pointcut-ref="bankPointcut"/>

</aop:config>

......

</beans>

由于使用了切点表达式,我们就不需要针对每一个业务类创建一个代理对象了。另外,如果配置的事务管理器Bean的名字取值为“transactionManager”,则我们可以省略<tx:advice>的transaction-manager属性,因为该属性的默认值即为“transactionManager”。

基于@Transactional的声明式事务管理

除了基于命名空间的事务配置方式,Spring2.x还引入了基于Annotation的方式,具体主要涉及@Transactional标注。@Transactional可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有public方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如清单12所示:

清单12.基于@Transactional的事务管理示例配置文件

@Transactional(propagation=Propagation.REQUIRED)

publicbooleantransfer(LongfromId,LongtoId,doubleamount){

returnbankDao.transfer(fromId,toId,amount);

}

Spring使用BeanPostProcessor来处理Bean中的标注,因此我们需要在配置文件中作如下声明来激活该后处理Bean,如清单13所示:

清单13.启用后处理Bean的配置

<tx:annotation-driventransaction-manager="transactionManager"/>

与前面相似,transaction-manager属性的默认值是transactionManager,如果事务管理器Bean的名字即为该值,则可以省略该属性。

虽然@Transactional注解可以作用于接口、接口方法、类以及类方法上,但是Spring小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外,@Transactional注解应该只被应用到public方法上,这是由SpringAOP的本质决定的。如果你在protected、private或者默认可见性的方法上使用@Transactional注解,这将被忽略,也不会抛出任何异常。

基于<tx>命名空间和基于@Transactional的事务声明方式各有优缺点。基于<tx>的方式,其优点是与切点表达式结合,功能强大。利用切点表达式,一个配置可以匹配多个方法,而基于@Transactional的方式必须在每一个需要使用事务的方法或者类上用@Transactional标注,尽管可能大多数事务的规则是一致的,但是对@Transactional而言,也无法重用,必须逐个指定。另一方面,基于@Transactional的方式使用起来非常简单明了,没有学习成本。开发人员可以根据需要,任选其中一种使用,甚至也可以根据需要混合使用这两种方式。

如果不是对遗留代码进行维护,则不建议再使用基于TransactionInterceptor以及基于TransactionProxyFactoryBean的声明式事务管理方式,但是,学习这两种方式非常有利于对底层实现的理解。

虽然上面共列举了四种声明式事务管理方式,但是这样的划分只是为了便于理解,其实后台的实现方式是一样的,只是用户使用的方式不同而已。

相关推荐