spring事务管理.doc
9.5. 声明式事务管理大多数Spring用户选择声明式事务管理。这是对应用代码影响最小的选择,因此也最符合 非侵入式 轻量级容器的理念。Spring的声明式事务管理是通过Spring AOP实现的,因为事务方面的代码与Spring绑定并以一种样板式风格使用,不过尽管如此,你一般并不需要理解AOP概念就可以有效地使用Spirng的声明式事务管理。从考虑EJB CMT和Spring声明式事务管理的相似以及不同之处出发是很有益的。它们的基本方法是相似的:都可以指定事务管理到单独的方法;如果需要可以在事务上下文调用 setRollbackOnly() 方法。不同之处在于:· 不像EJB CMT绑定在JTA上,Spring声明式事务管理可以在任何环境下使用。只需更改配置文件,它就可以和JDBC、JDO、Hibernate或其他的事务机制一起工作。· Spring的声明式事务管理可以被应用到任何类(以及那个类的实例)上,不仅仅是像EJB那样的特殊类。· Spring提供了声明式的回滚规则:EJB没有对应的特性,我们将在下面讨论。回滚可以声明式的控制,不仅仅是编程式的。· Spring允许你通过AOP定制事务行为。例如,如果需要,你可以在事务回滚中插入定制的行为。你也可以增加任意的通知,就象事务通知一样。使用EJB CMT,除了使用setRollbackOnly(),你没有办法能够影响容器的事务管理。· Spring不提供高端应用服务器提供的跨越远程调用的事务上下文传播。如果你需要这些特性,我们推荐你使用EJB。然而,不要轻易使用这些特性。通常我们并不希望事务跨越远程调用。TransactionProxyFactoryBean在哪儿?Spring2.0及以后的版本中声明式事务的配置与之前的版本有相当大的不同。主要差异在于不再需要配置TransactionProxyFactoryBean了。Spring2.0之前的旧版本风格的配置仍然是有效的;你可以简单地认为新的<tx:tags/>替你定义了TransactionProxyFactoryBean。回滚规则的概念比较重要:它使我们能够指定什么样的异常(和throwable)将导致自动回滚。我们在配置文件中声明式地指定,无须在Java代码中。同时,我们仍旧可以通过调用 TransactionStatus 的 setRollbackOnly() 方法编程式地回滚当前事务。通常,我们定义一条规则,声明 MyApplicationException 必须总是导致事务回滚。这种方式带来了显著的好处,它使你的业务对象不必依赖于事务设施。典型的例子是你不必在代码中导入Spring API,事务等。对EJB来说,默认的行为是EJB容器在遇到 系统异常(通常指运行时异常)时自动回滚当前事务。EJB CMT遇到 应用异常(例如,除了 java.rmi.RemoteException 外别的checked exception)时并不会自动回滚。默认式Spring处理声明式事务管理的规则遵守EJB习惯(只在遇到unchecked exceptions时自动回滚),但通常定制这条规则会更有用。9.5.1. 理解Spring的声明式事务管理实现本节的目的是消除与使用声明式事务管理有关的神秘性。简单点儿总是好的,这份参考文档只是告诉你给你的类加上Transactional注解,在配置文件中添加('<tx:annotation-driven/>')行,然后期望你理解整个过程是怎么工作的。此节讲述Spring的声明式事务管理内部的工作机制,以帮助你在面对事务相关的问题时不至于误入迷途,回朔到上游平静的水域。提示阅读Spring源码是理解清楚Spring事务支持的一个好方法。Spring的Javadoc提供的信息丰富而完整。我们建议你在开发自己的Spring应用时把日志级别设为'DEBUG'级,这样你能更清楚地看到幕后发生的事。在理解Spring的声明式事务管理方面最重要的概念是:Spring的事务管理是通过AOP代理实现的。其中的事务通知由元数据(目前基于XML或注解)驱动。代理对象与事务元数据结合产生了一个AOP代理,它使用一个PlatformTransactionManager实现品配合TransactionInterceptor,在方法调用前后实施事务。注意尽管使用Spring声明式事务管理不需要AOP(尤其是Spring AOP)的知识,但了解这些是很有帮助的。你可以在 章找到关于Spring AOP的全部内容。概念上来说,在事务代理上调用方法的工作过程看起来像这样:9.5.2. 第一个例子请看下面的接口和它的实现。这个例子的意图是介绍概念,使用 Foo 和 Bar 这样的名字只是为了让你关注于事务的用法,而不是领域模型。<!- 我们想做成事务性的服务接口 -> package x.y.service;public interface FooService Foo getFoo(String fooName); Foo getFoo(String fooName, String barName); void insertFoo(Foo foo); void updateFoo(Foo foo);<!- 上述接口的一个实现 -> package x.y.service;public class DefaultFooService implements FooService public Foo getFoo(String fooName) throw new UnsupportedOperationException(); public Foo getFoo(String fooName, String barName) throw new UnsupportedOperationException(); public void insertFoo(Foo foo) throw new UnsupportedOperationException(); public void updateFoo(Foo foo) throw new UnsupportedOperationException(); (对该例的目的来说,上例中实现类(DefaultFooService)的每个方法在其方法体中抛出 UnsupportedOperationException 的做法是恰当的,我们可以看到,事务被创建出来,响应 UnsupportedOperationException 的抛出,然后回滚。) 我们假定,FooService的前两个方法(getFoo(String)和getFoo(String, String))必须执行在只读事务上下文中,其余方法(insertFoo(Foo)和updateFoo(Foo))必须执行在读写事务上下文中。使用XML方式元数据的声明式配置的话,你得这么写(不要想着一次全部理解,所有内容会在后面的章节详细讨论):<!- 'context.xml'文件的内容如下: -> <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http:/www.springframework.org/schema/beans" xmlns:xsi="http:/www.w3.org/2001/XMLSchema-instance" xmlns:aop="http:/www.springframework.org/schema/aop" xmlns:tx="http:/www.springframework.org/schema/tx" xsi:schemaLocation=" http:/www.springframework.org/schema/beans http:/www.springframework.org/schema/beans/spring-beans-2.0.xsd http:/www.springframework.org/schema/tx http:/www.springframework.org/schema/tx/spring-tx-2.0.xsd http:/www.springframework.org/schema/aop http:/www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <!- 这是我们将要配置并使它具有事务性的Service对象 -> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!- the transactional advice (i.e. what 'happens' see the <aop:advisor/> bean below) -> <tx:advice id="txAdvice" transaction-manager="txManager"> <!- the transactional semantics. -> <tx:attributes> <!- all methods starting with 'get' are read-only -> <tx:method name="get*" read-only="true"/> <!- other methods use the default transaction settings (see below) -> <tx:method name="*"/> </tx:attributes> </tx:advice> <!- ensure that the above transactional advice runs for any execution of an operation defined by the FooService interface -> <aop:config> <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(.)"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/> </aop:config> <!- don't forget the DataSource -> <bean id="dataSource" class="mons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> <property name="url" value="jdbc:oracle:thin:rj-t42:1521:elvis"/> <property name="username" value="scott"/> <property name="password" value="tiger"/> </bean> <!- similarly, don't forget the (particular) PlatformTransactionManager -> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!- other <bean/> definitions here -> </beans>我们来分析一下上面的配置。我们要把一个服务对象('fooService' bean)做成事务性的。我们想施加的事务语义封装在<tx:advice/>定义中。<tx:advice/> “把所有以 'get' 开头的方法看做执行在只读事务上下文中,其余的方法执行在默认语义的事务上下文中”。 其中的 'transaction-manager' 属性被设置为一个指向 PlatformTransactionManager bean的名字(这里指 'txManager'),该bean将实际上实施事务管理。提示事实上,如果 PlatformTransactionManager bean的名字是 'transactionManager' 的话,你的事务通知(<tx:advice/>)中的 'transaction-manager' 属性可以忽略。否则你则需要像上例那样明确指定。配置中最后一段是 <aop:config/> 的定义,它确保由 'txAdvice' bean定义的事务通知在应用中合适的点被执行。首先我们定义了 一个切面,它匹配 FooService 接口定义的所有操作,我们把该切面叫做 'fooServiceOperation'。然后我们用一个通知器(advisor)把这个切面与 'txAdvice' 绑定在一起,表示当 'fooServiceOperation' 执行时,'txAdvice' 定义的通知逻辑将被执行。<aop:pointcut/> 元素定义是AspectJ的切面表示法,可参考Spring 2.0 章获得更详细的内容。一个普遍性的需求是让整个服务层成为事务性的。满足该需求的最好方式是让切面表达式匹配服务层的所有操作方法。例如:<aop:config> <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(.)"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/> </aop:config>(这个例子中假定你所有的服务接口定义在 'x.y.service' 包中。你同样可以参考 章获得更详细内容。) 现在,既然我们已经分析了整个配置,你可能会问了,“好吧,但是所有这些配置做了什么?”。上面的配置将为由 'fooService' 定义的bean创建一个代理对象,这个代理对象被装配了事务通知,所以当它的相应方法被调用时,一个事务将被启动、挂起、被标记为只读,或者其它(根据该方法所配置的事务语义)。我们来看看下面的例子,测试一下上面的配置。public final class Boot public static void main(final String args) throws Exception ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class); FooService fooService = (FooService) ctx.getBean("fooService"); fooService.insertFoo (new Foo(); 运行上面程序的输出结果看起来像这样(注意为了清楚起见,Log4J的消息和从 DefaultFooService 的 insertFoo(.) 方法抛出的 UnsupportedOperationException 异常堆栈信息被省略了)。<!- Spring容器开始启动. ->AspectJInvocationContextExposingAdvisorAutoProxyCreator - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors <!- the DefaultFooService is actually proxied -> JdkDynamicAopProxy - Creating JDK dynamic proxy for x.y.service.DefaultFooService <!- . the insertFoo(.) method is now being invoked on the proxy -> TransactionInterceptor - Getting transaction for x.y.service.FooService.insertFoo <!- the transactional advice kicks in here. -> DataSourceTransactionManager - Creating new transaction with name x.y.service.FooService.insertFooDataSourceTransactionManager - Acquired Connection mons.dbcp.PoolableConnectiona53de4 for JDBC transaction <!- the insertFoo(.) method from DefaultFooService throws an exception. -> RuleBasedTransactionAttribute - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationExceptionTransactionInterceptor - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable java.lang.UnsupportedOperationException <!- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -> DataSourceTransactionManager - Rolling back JDBC transaction on Connection mons.dbcp.PoolableConnectiona53de4DataSourceTransactionManager - Releasing JDBC Connection after transactionDataSourceUtils - Returning JDBC Connection to DataSourceException in thread "main" java.lang.UnsupportedOperationExceptionat x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14) <!- AOP infrastructure stack trace elements removed for clarity -> at $Proxy0.insertFoo(Unknown Source)at Boot.main(Boot.java:11)9.5.3. 回滚在前面的章节里,概述了如何在你的应用里为类特别是服务层的类指定事务性的基本方法。这一章将描述在一个简单的声明式配置中如何你才能控制事务的回滚。一个容易的(和被推荐的)方法是在Spring框架的事务架构里指出当context的事务里的代码抛出 Exception 时事务进行回滚。Spring框架的事务基础架构代码将从调用的堆栈里捕获到任何未处理的 Exception,并将标识事务将回滚。 然而,请注意Spring框架的事务基础架构代码将默认地 只 在抛出运行时和unchecked exceptions时才标识事务回滚。 也就是说,当抛出一个 RuntimeException 或其子类例的实例时。(Errors 也一样 - 默认地 - 标识事务回滚。)从事务方法中抛出的Checked exceptions将 不 被标识进行事务回滚。 就是这些默认的设置;严格规定了哪些 Exception 类型将被标识进行事务回滚。 下面的XML配置片断里示范了如何配置一个checked,应用程序指定的 Exception 类型来标识事务回滚。 <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="false" rollback-for="NoProductInStockException"/> <tx:method name="*"/> </tx:attributes></tx:advice>第二方法是在Spring框架的事务架构里通过 编程式 方式指出一个事务将被回滚。 虽然这个非常简单,但是这个方法对于Spring框架的事务架构来说,在你的代码是高入侵的和紧藕合的 下面的代码片断里示范了Spring框架管理事务的编程式回滚: public void resolvePosition() try / some business logic. catch (NoProductInStockException ex) / trigger rollback programmatically TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 强烈推荐你尽可能地使用声明式事务回滚方法。 编程式方法的回滚对你来说是可见,如果你需要它你就可以使用,但是使用它就直接违反了在你的应用中使用一个纯基于POJO的模型。 9.5.4. 为不同的bean配置不同的事务语义现在我们考虑一下这样的场景,你有许多服务对象,而且想为不同组的对象设置 完全不同 的事务语义。在Spring中,你可以通过定义各自特定的 <aop:advisor/> 元素,每个advisor采用不同的 'pointcut' 和 'advice-ref' 属性,来达到目的。借助于一个例子,我们假定你所有的服务层类定义在以 'x.y.service' 为根的包内。为了让service包(或子包)下所有名字以 'Service' 结尾的类的对象拥有默认的事务语义,你可以配置如下:<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http:/www.springframework.org/schema/beans" xmlns:xsi="http:/www.w3.org/2001/XMLSchema-instance" xmlns:aop="http:/www.springframework.org/schema/aop" xmlns:tx="http:/www.springframework.org/schema/tx" xsi:schemaLocation=" http:/www.springframework.org/schema/beans http:/www.springframework.org/schema/beans/spring-beans-2.0.xsd http:/www.springframework.org/schema/tx http:/www.springframework.org/schema/tx/spring-tx-2.0.xsd http:/www.springframework.org/schema/aop http:/www.springframework.org/schema/aop/spring-aop-2.0.xsd"><aop:config> <aop:pointcut id="serviceOperation" expression="execution(* x.y.service.*Service.*(.)"/> <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/></aop:config><!- these two beans will be transactional. -><bean id="fooService" class="x.y.service.DefaultFooService"/><bean id="barService" class="x.y.service.extras.SimpleBarService"/><!- .and these two beans won't -> <bean id="anotherService" class="org.xyz.SomeService"/> <!- (not in the right package) -> <bean id="barManager" class="x.y.service.SimpleBarManager"/> <!- (doesn't end in 'Service') -> <tx:advice id="txAdvice"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!- other transaction infrastructure beans such as a PlatformTransactionManager omitted. -></beans>下面的配置示例演示了两个不同的bean拥有完全不同的事务配置。 <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http:/www.springframework.org/schema/beans" xmlns:xsi="http:/www.w3.org/2001/XMLSchema-instance" xmlns:aop="http:/www.springframework.org/schema/aop" xmlns:tx="http:/www.springframework.org/schema/tx" xsi:schemaLocation=" http:/www.springframework.org/schema/beans http:/www.springframework.org/schema/beans/spring-beans-2.0.xsd http:/www.springframework.org/schema/tx http:/www.springframework.org/schema/tx/spring-tx-2.0.xsd http:/www.springframework.org/schema/aop http:/www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <aop:config> <aop:pointcut id="defaultServiceOperation" expression="execution(* x.y.service.*Service.*(.)"/> <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/> <aop:pointcut id="noTxServiceOperation" expression="execution(* x.y.service.ddl.DefaultDdlManager.*(.)"/> <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/> </aop:config> <!- this bean will be transactional (c.f. the 'defaultServiceOperation' pointcut) -> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!- this bean will also be transactional, but with totally different transactional settings -> <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/> <tx:advice id="defaultTxAdvice"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <tx:advice id="noTxAdvice"> <tx:attributes> <tx:method name="*" propagation="NEVER"/> </tx:attributes> </tx:advice> <!- other transaction infrastructure beans such as a PlatformTransactionManager omitted. -></beans>9.5.5. <tx:advice/> 有关的设置这一节里将描述通过 <tx:advice/> 标签来指定不同的事务性设置。默认的 <tx:advice/> 设置如下: · 事务传播设置是 REQUIRED· 隔离级别是 DEFAULT· 事务是 读/写· 事务超时默认是依赖于事务系统的,或者事务超时没有被支持。· 任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚这些默认的设置当然也是可以被改变的。 <tx:advice/> 和 <tx:attributes/> 标签里的 <tx:method/> 各种属性设置总结如下: 表 9.1. <tx:method/> 有关的设置属性是否需要?默认值描述name是 与事务属性关联的方法名。通配符(*)可以用来指定一批关联到相同的事务属性的方法。 如:'get*'、'handle*'、'on*Event'等等。 propagation不REQUIRED事务传播行为isolation不DEFAULT事务隔离级别timeout不-1事务超时的时间(以秒为单位)read-only不false事务是否只读?rollback-for不 将被触发进行回滚的 Exception(s);以逗号分开。 如:'com.foo.MyBusinessException,ServletException' no-rollback-for不 不 被触发进行回滚的 Exception(s);以逗号分开。 如:'com.foo.MyBusinessException,ServletException' 9.5.6. 使用 Transactional注意Transactional 注解及其支持类所提供的功能最低要求使用Java 5(Tiger)。 除了基于XML文件的声明式事务配置外,你也可以采用基于注解式的事务配置方法。直接在Java源代码中声明事务语义的做法让事务声明和将受