Spring事务管理
全面的事务支持是使用Spring框架的最有说服力的原因之一。Spring框架为事务管理提供了一致的抽象,它提供了以下好处:
- 跨不同事务API的一致编程模型,如Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API (JPA)。
- 支持声明性事务管理。
- 提供更简单的编程事务管理API。
- 与Spring的数据访问抽象进行了极好的集成。
一、Spring框架事务模型的优点
传统上,Java EE开发人员有两种事务管理选择:全局事务或本地事务,这两种方法都有很大的局限性。接下来的两个部分将回顾全局和本地事务管理,然后讨论Spring框架的事务管理支持如何解决全局和本地事务模型的局限性。
全局事务
全局事务允许你处理多个事务性资源,通常是关系数据库和消息队列。应用服务器通过JTA管理全局事务,JTA是一个繁琐的API(部分原因是因为它的异常模型)。此外,一个JTA事务通常需要来自JNDI,这意味着你还需要使用JNDI才能使用JTA。全局事务的使用限制了应用程序代码的任何潜在重用,因为JTA通常只在应用程序服务器环境中可用。
以前,使用全局事务的首选方法是通过EJB-CMT(容器管理的事务)。CMT是一种声明性事务管理(与编程事务管理不同)。EJB-CMT消除了与事务相关的JNDI查找的需要,尽管EJB的使用本身就需要使用JNDI。它不需要编写Java代码来控制事务,但它最大的缺点是CMT与JTA和应用服务器环境绑定在一起。此外,只有当选择在EJB中实现业务逻辑(或者至少在事务性EJB facade后面)时,它才可用。一般来说,EJB的缺点是如此之大,以至于这不是一个有吸引力的建议,尤其是在声明性事务管理的令人信服的替代方案面前。
本地事务
本地事务是特定于资源的,例如与JDBC连接相关联的事务。本地事务可能更易于使用,但有一个明显的缺点:它们不能跨多个事务性资源工作。例如,使用JDBC连接管理事务的代码不能在全局JTA事务中运行。因为应用服务器不参与事务管理,所以它不能确保跨多个资源操作的正确性。(值得注意的是,大多数应用程序使用单一的事务资源。)另一个缺点是本地事务对编程模型具有侵入性。
Spring框架的一致性编程模型
Spring解决了全局事务和本地事务的缺点。它允许应用程序开发人员在任何环境中使用一致的编程模型。你只需编写一次代码,就可以从不同环境中的不同事务管理策略中获益。Spring框架提供了声明性事务管理和编程事务管理。大多数用户更喜欢声明性事务管理,我们建议在大多数情况下使用它。
通过编程事务管理,开发人员使用Spring框架事务抽象,它可以运行在任何底层事务基础设施之上。使用首选的声明性模型,开发人员通常很少编写或不编写与事务管理相关的代码,因此不依赖于Spring框架事务API或任何其他事务API。
你是否需要应用程序服务器进行事务管理?
Spring框架的事务管理支持改变了企业Java应用程序何时需要应用程序服务器的传统规则。
特别是,您不需要纯粹用于通过EJB进行声明性事务的应用服务器。事实上,即使您的应用服务器具有强大的JTA功能,您也可能认为Spring框架的声明性事务比EJB-CMT提供了更强大的功能和更高效的编程模型。通常,只有当应用程序需要处理跨多个资源的事务时,才需要应用程序服务器的JTA功能,但这并不是许多应用程序的要求。许多高端应用程序使用单个、高度可扩展的数据库(如Oracle RAC)。独立事务管理器(如Atomikos事务和JOTM)是其他选择。当然,你可能需要其他应用程序服务器功能,例如Java消息服务(JMS)和Java EE连接器体系结构(JCA)。
Spring框架让你可以选择何时将应用程序扩展到完全加载的应用程序服务器。使用EJB-CMT或JTA的唯一替代方法是用本地事务(例如JDBC连接上的事务)编写代码,如果需要代码在全局容器管理的事务中运行,那么将面临大量的返工。在Spring框架中,只有配置文件中的一些bean定义需要更改(而不是代码)。
二、理解Spring框架事务抽象
Spring事务抽象的关键是事务策略的概念。事务策略由org.springframework.transaction.PlatformTransactionManager接口定义。
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
这主要是一个服务提供者接口(Service Provider Interface,SPI),尽管你可以从应用程序代码中以编程方式使用它,但是不建议这样做。PlatformTransactionManager是一个接口,可以根据需要轻松地模拟或存根。它与查找策略(如JNDI)无关。PlatformTransactionManager实现的定义方式与Spring框架IoC容器中的任何其他对象(或bean)相同。这一优点使Spring框架事务成为一个有价值的抽象,即使在使用JTA时也是如此。与直接使用JTA相比,你可以更容易地测试事务性代码。
同样,为了与Spring的理念保持一致,可以由PlatformTransactionManager接口的任何方法抛出的未检查异常TransactionException(也就是说,它扩展了java.lang.RuntimeException类)。事务基础设施故障几乎总是致命的。在极少数情况下,应用程序代码可以从事务失败中恢复,应用程序开发人员仍然可以选择捕获和处理TransactionException。突出的一点是,开发商并不强制这么做。
getTransaction(..)方法根据TransactionDefinition参数返回TransactionStatus对象。如果当前调用堆栈中存在匹配的事务,则返回的TransactionStatus可能表示新事务或现有事务。后一种情况的含义是,与Java EE事务上下文一样,TransactionStatus与执行线程相关联。
TransactionDefinition接口指定:
-
事务传播:通常,在事务范围内执行的所有代码都在该事务中运行。但是,如果在事务上下文已经存在时执行事务方法,则可以指定行为。例如,代码可以在现有事务中继续运行(常见情况),也可以挂起现有事务并创建新事务。Spring提供了EJB-CMT中熟悉的所有事务传播选项。
- 事务隔离:此事务与其他事务的工作隔离的程度。例如,此事务是否可以查看其他事务的未提交写入等。
- 事务超时:此事务在超时并被底层事务基础结构自动回滚之前运行多长时间。
- 只读状态:当代码读取但不修改数据时,可以使用只读事务。在某些情况下,只读事务可能是一种有用的优化,例如当您使用Hibernate时。。
这些设置反映了标准的事务性概念。
TransactionStatus接口为事务代码提供了控制事务执行和查询事务状态的简单方法。这些概念应该很熟悉,因为它们对所有事务api都是通用的。下表显示了TransactionStatus接口:
public interface TransactionStatus extends SavepointManager { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); void flush(); boolean isCompleted(); }
无论你在Spring中选择声明式事务管理还是编程式事务管理,定义正确的PlatformTransactionManager实现都是绝对必要的。你通常通过依赖注入来定义这个实现。
PlatformTransactionManager实现通常需要了解其工作环境:JDBC、JTA、Hibernate等。下面的示例展示了如何定义本地PlatformTransactionManager实现(在本例中,使用普通JDBC)
你可以通过创建类似于以下内容的bean来定义JDBC数据源:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean>
然后,相关的PlatformTransactionManager 对数据源定义进行引用。
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
如果你在Java EE容器中使用JTA,那么你将使用通过JNDI获得的容器数据源,并结合Spring的JtaTransactionManager。以下示例显示JTA和JNDI的查找:
<?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:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jee https://www.springframework.org/schema/jee/spring-jee.xsd"> <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/> <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" /> <!-- other <bean/> definitions here --> </beans>
JtaTransactionManager不需要知道数据源(或任何其他特定资源),因为它使用容器的全局事务管理基础设施。
你还可以轻松地使用Hibernate本地事务,如下例所示。在本例中,你需要定义一个Hibernate LocalSessionFactoryBean,应用程序代码可以使用它来获取Hibernate会话实例。
DataSource bean定义类似于前面所示的本地JDBC示例。
如果DataSource(由任何非JTA事务管理器使用)是通过JNDI查找的,那么它应该是非事务性的,因为Spring框架(而不是Java EE容器)管理事务。
本例中的txManager bean属于HibernateTransactionManager类型。正如DataSourceTransactionManager需要对数据源的引用一样,HibernateTransactionManager需要对SessionFactory的引用。以下示例声明sessionFactory和txManager bean:
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mappingResources"> <list> <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=${hibernate.dialect} </value> </property> </bean> <bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean>
如果使用Hibernate和Java EE容器管理的JTA事务,则应该使用与前面JDBC JTA示例中相同的JtaTransactionManager,如下例所示:
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
如果你使用JTA,那么事务管理器定义是类似的,不管你使用什么数据访问技术,不管它是JDBC、Hibernate JPA还是任何其他受支持的技术。这是因为JTA事务是全局事务,可以包含任何事务资源。
在所有这些情况下,应用程序代码不需要更改。你可以通过更改配置来更改事务的管理方式,即使这种更改是从本地事务转移到全局事务,或者反之亦然。
三、将资源与事务同步
如何创建不同的事务管理器,以及它们如何链接到需要同步到事务的相关资源(例如,DataSourceTransactionManager到JDBC数据源,HibernateTransactionManager到Hibernate SessionFactory等等)现在应该很清楚了。本节描述应用程序代码(通过使用持久化API(如JDBC、Hibernate或JPA)直接或间接地)如何确保这些资源被正确地创建、重用和清理。本节还讨论如何通过相关的PlatformTransactionManager触发事务同步。
高级抽象的同步方法
首选的方法是使用Spring最高级的基于模板的持久性集成APIs,或者使用带有事务感知的工厂bean或代理的本地ORM APIs来管理本地资源工厂。这些事务感知解决方案在内部处理资源的创建和重用、清理、资源的可选事务同步以及异常映射。因此,用户数据访问代码不必处理这些任务细节。通常,你使用本地 ORM APIs或通过使用JdbcTemplate对JDBC进行访问。
底层的同步方法
DataSourceUtils(用于JDBC)、EntityManagerFactoryUtils(用于JPA)、SessionFactoryUtils(用于Hibernate)这些类都是较为底层的。当你希望在Spring框架中正确地映射这些应用程序的持久性时,你可以选择使用这些持久性类来处理这些异常。
例如,对于JDBC,你可以使用Spring的org.springframework.jdbc.datasource.DataSourceUtils类,如下所示:
Connection conn = DataSourceUtils.getConnection(dataSource);
如果现有事务已经有一个与之同步(链接)的连接,则返回该实例。否则,方法调用将触发新连接的创建,该连接与任何现有事务同步,并可在同一事务中供后续重用。任何SQLException都封装在Spring框架CannotGetJdbcConnectionException中,如Spring框架的未检查DataAccessException。这种方法为你提供了比从SQLException轻松获得更多信息,并确保了跨数据库甚至跨不同持久性技术的可移植性。
这种方法在没有Spring事务管理的情况下也可以工作(事务同步是可选的),因此无论是否使用Spring进行事务管理,都可以使用它。
当然,一旦您使用了Spring的JDBC支持、JPA支持或Hibernate支持,、你通常不希望使用DataSourceUtils或其他helper类,因为通过Spring抽象工作比直接使用相关api要好的多。例如,使用JdbcTemplate 或 jdbc.object包化简化你对JDBC的使用,正确的连接检索发生在后台,你不需要编写任何特殊的代码。
TransactionAwareDataSourceProxy
在最底层存在TransactionWareDatasourceProxy类。这是一个目标数据源的代理,它包装了目标数据源以增加对Spring管理事务的感知。在这方面,它类似于事务性JNDI数据源,由Java EE服务器提供。
除了必须调用现有代码并传递标准JDBC数据源接口实现时,你几乎永远不需要或不想使用这个类。在这种情况下,这段代码可能是可用的,但它参与了Spring管理的事务。你可以使用前面提到的高级抽象来编写新代码。
四、声明性事务管理
大多数Spring框架用户选择声明式事务管理。此选项对应用程序代码的影响最小,因此与非侵入式轻量级容器的理念最为一致。
Spring框架的声明性事务管理是通过Spring面向方面编程(AOP)实现的。
但是因为与事务相关的切面代码是随着框架一起发布的,所以你不用理解AOP的相关概念就可以使用了。
Spring框架的声明性事务管理类似于EJB-CMT,可以指定事务行为(或者不指定事务行为)到单个方法级别。如果需要,可以在事务上下文中调用setRollbackOnly()。这两种类型的事务管理区别在于:
- 与EJB-CMT(绑定到JTA)不同,Spring框架的声明性事务管理可以在任何环境中工作。它可以使用JDBC、JPA或Hibernate通过调整配置文件来处理JTA事务或本地事务。
- 可以将Spring框架声明性事务管理应用于任何类,而不仅仅是像EJB这样的特殊类。
- Spring框架提供了声明性回滚规则,EJB没有与之等效的特性。
- Spring框架允许你使用AOP定制事务行为。例如,可以在事务回滚的情况下插入自定义行为。还可以添加任意建议以及事务性建议。使用EJB CMT,除了使用setRollbackOnly()之外,你不能影响容器的事务管理。
- Spring框架不像高端应用服务器那样支持跨远程调用传播事务上下文。如果你需要这个特性,我们建议你使用EJB。
回滚规则的概念很重要。它们允许你指定哪些异常(和可丢弃的)应该自动回滚。你可以在配置中以声明方式指定,而不是在Java代码中指定。尽管你仍然可以对TransactionStatus对象调用setRollbackOnly()来回滚当前事务,但大多数情况下,可以指定一个规则,如MyApplicationException必须始终回滚。此选项的显著优点是业务对象不依赖于事务基础设施。例如,它们通常不需要导入Spring事务api或其他spring api。
尽管EJB容器默认行为在系统异常(通常是运行时异常)时自动回滚事务,但EJB-CMT不会自动回滚(除了java.rmi.RemoteException)。虽然声明性事务管理的Spring默认行为遵循EJB约定(回滚仅在未检查的异常时自动进行),但是定制这种行为通常很有用。
理解Spring框架的声明性事务实现
仅仅告诉你用@Transactional注解来注解类,将@EnableTransactionManagement添加到配置中,并期望你理解它是如何工作的,这是远远不够的。为了提供更深入的理解,本小节将解释Spring框架的声明性事务基础设施在发生与事务相关的问题时的内部工作方式。
关于Spring框架的声明性事务支持,需要掌握的最重要的概念是,这种支持是通过AOP代理实现的,并且事务建议是由元数据(当前基于XML或注解)驱动的。AOP与事务性元数据的结合产生了一个AOP代理,该代理使用TransactionInterceptor和适当的PlatformTransactionManager实现来驱动围绕方法调用的事务。
下图显示了在事务代理上调用方法的流程:
声明性事务实现示例
考虑下面的接口及其实现。这个例子使用Foo和Bar类,这样你就可以专注于事务的使用,而不必关注特定的实体模型。在这个例子中,DefaultFooService类在每个实现方法的主体中抛出UnsupportedOperationException异常。该行为允许你看到事务被创建,然后回滚以响应UnsupportedOperationException实例的过程。
FooService接口:
public interface FooService { Foo getFoo(String fooName); Foo getFoo(String fooName, String barName); void insertFoo(Foo foo); void updateFoo(Foo foo); }
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(); } }
假设FooService接口的前两个方法getFoo(String)和getFoo(String,String)必须在具有只读语义的事务上下文中执行,而其他方法insertFoo(Foo)和updateFoo(Foo)必须在具有读写语义的事务上下文中执行。下面几段将详细解释以下配置:
<!-- from the file '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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- this is the service object that we want to make transactional --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- the transactional advice (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="org.apache.commons.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 PlatformTransactionManager --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- other <bean/> definitions here --> </beans>
上面的例子中,它假设您希望使服务对象fooService成为事务性的。
要应用的事务语义封装在<tx:advice/>定义中。<tx:advice/>定义为“所有方法,从get开始,将在只读事务的上下文中执行,而所有其他方法将使用默认事务语义执行”。<tx:advice/>标记的transaction-manager属性被设置为驱动事务的PlatformTransactionManager bean的名称(在本例中为txManager bean)。
如果要连接的PlatformTransactionManager的bean名称的名称为transactionManager,则可以省略事务通知(<tx:advice/>)中的事务管理器属性。如果要连接的PlatformTransactionManager bean是其他名称,则必须显式使用事务管理器属性。
在<aop:config/>定义确保txAdvice bean定义的事务通知在程序中的适当点执行。首先,定义一个与FooService接口中定义的任何操作的执行相匹配的切入点。然后使用advisor将切入点与txAdvice关联起来。结果表明,在执行操作时,txAdvice定义的通知被运行。
<aop:pointcut/> 是AspectJ切入点表达式。
一个常见的需求是使整个服务层成为事务性的。最好的方法是更改切入点表达式以匹配服务层中的任何操作。下面的示例演示如何执行此操作(假设你的所有服务接口都在x.y.service包中定义。):
<aop:config> <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/> </aop:config>
既然我们已经分析了配置,你可能会问自己:所有这些配置实际上都做了什么?我们可以通过运行前面的例子观看输出
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()); } }
运行上述程序,输出内容应该类似于以下内容(为了清晰起见,DefaultFooService类的insertFoo(..)方法引发的UnsupportedOperationException的Log4J输出和堆栈跟踪被截断):
<!-- the Spring container is starting up... --> [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.insertFoo] [DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction <!-- the insertFoo(..) method from DefaultFooService throws an exception... --> [RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException [TransactionInterceptor] - 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 [org.apache.commons.dbcp.PoolableConnection@a53de4] [DataSourceTransactionManager] - Releasing JDBC Connection after transaction [DataSourceUtils] - Returning JDBC Connection to DataSource Exception in thread "main" java.lang.UnsupportedOperationException at 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)
声明性事务回滚
上一节讲述了如何在应用程序中以声明方式为类(通常是服务层类)指定事务性设置的基本知识。本节介绍如何以简单的声明性方式控制事务的回滚。
在默认配置中,Spring框架的事务基础结构代码仅在发生未检查异常的情况下才进行回滚,即抛出的异常是RuntimeException的实例或子类。(默认情况下,错误实例也会导致回滚)。从事务方法引发的已检查异常不会导致默认配置中的回滚。
你可以精确地配置哪些异常类型标记事务以进行回滚
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
如果不希望在引发异常时回滚事务,也可以指定“无回滚规则”。
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/> <tx:method name="*"/> </tx:attributes> </tx:advice>
当Spring框架的事务基础结构捕捉到异常,并参考配置的回滚规则来确定是否将事务标记为回滚时,最高级别的匹配规则将获胜。因此,在以下配置的情况下,除InstrumentNotFoundException之外的任何异常都会导致助理事务的回滚:
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/> </tx:attributes> </tx:advice>
你也可以通过编程方式指定所需的回滚。虽然很简单,但是这个过程是非常有侵略性的,它将你的代码与Spring框架的事务基础设施紧密地耦合在一起。以下示例演示如何以编程方式指示所需的回滚:
public void resolvePosition() { try { // some business logic... } catch (NoProductInStockException ex) { // trigger rollback programmatically TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
为不同的bean配置不同的事务语义
考虑这样一个场景:你有许多服务层对象,并且希望对每个对象应用完全不同的事务配置。
首先假设你的所有服务层类都定义在根x.y.service包中。要使作为该包(或子包)中定义的类的实例且名称以Service结尾的所有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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:config> <aop:pointcut id="defaultServiceOperation" expression="execution(* x.y.service.*Service.*(..))"/> <aop:pointcut id="noTxServiceOperation" expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/> <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/> <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/> </aop:config> <!-- this bean will be transactional (see 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>
<tx:advice/> 设置
本节总结了可以使用<tx:advice/>标记指定的各种事务设置。默认的<tx:advice/>设置为:
- 事务传播:REQUIRED
- 隔离级别:DEFAULT
- 事务是read-write
- 事务超时默认为基础事务系统的默认超时,如果不支持超时,则默认为无。
- 任何RuntimeException都会触发回滚
你可以更改这些默认设置。下表总结了嵌套在<tx:advice/>和<tx:attributes/>标记中的<tx:method/>标记的各种属性:
<tx:method/> 设置
- name:必须。要与事务属性关联的方法名。通配符(*)可用于将相同的事务属性设置与许多方法(例如,get*、handle*、on*Event等)关联起来。
- propagation:默认REQUIRED
- isolation:事务隔离级别,默认是DEFAULT,仅适用于REQUIRED或REQUIRES\U NEW的传播设置。
- timeout:默认值-1。事务超时(秒)。仅适用于需要或需要更新的传播。
- read-only:默认false。读写与只读事务。仅适用于REQUIRED或REQUIRES-NEW。
- rollback-for:触发回滚的异常实例的逗号分隔列表。例如com.foo.MyBusinessException,ServletException
- no-rollback-for:不触发回滚的异常实例的逗号分隔列表。例如com.foo.MyBusinessException,ServletException
使用@Transactional
除了基于XML的声明性事务配置方法外,还可以使用基于注释的方法。直接在Java源代码中声明事务语义,可以使声明更接近受影响的代码。耦合的危险并不大,因为本来要以事务方式使用的代码几乎总是以这种方式部署的。
以下示例使用@Transactional注解
// the service class that we want to make transactional @Transactional public class DefaultFooService implements FooService { Foo getFoo(String fooName); Foo getFoo(String fooName, String barName); void insertFoo(Foo foo); void updateFoo(Foo foo); }
上述例子@Transactional是在类级别上使用,声明类(及其子类)的所有方法的默认值。或者每个方法都可以应用到单独得到注解。请注意,类级别的注解不适用于类层次结构上的祖先类;在这种情况下,需要在本地重新声明方法才能参与子类级别的注解。
当像上面这样的POJO类被定义为Spring上下文中的bean时,可以通过@Configuration类中的@EnableTransactionManagement注解使bean实例成为事务性的。
在XML配置中,<tx:annotation-driven/>标记提供了类似的便利:
<!-- from the file '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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- this is the service object that we want to make transactional --> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- enable the configuration of transactional behavior based on annotations --> <tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- (this dependency is defined somewhere else) --> <property name="dataSource" ref="dataSource"/> </bean> <!-- other <bean/> definitions here --> </beans>
方法可见性和@Transactional
使用代理时,应仅将@Transactional注解应用于具有公共可见性的方法。如果使用@Transactional对受保护的、私有的或包可见的方法进行注解,虽不会引发错误,但带注解的方法不会显示配置的事务设置。如果需要注解非公共方法,请考虑使用AspectJ。
可以将@Transactional注解应用于接口定义、接口上的方法、类定义或类上的公共方法。然而,仅仅存在@Transactional注解不足以激活事务行为。@Transactional注释仅仅是一些运行时基础设施可以使用的元数据,这些基础设施具有@Transactional意识,可以使用元数据配置具有事务行为的适当bean。在前面的示例中,<tx:annotation-driven/>元素打开事务行为。
Spring团队建议使用@Transactional注解仅对具体类(以及具体类的方法)进行注释,而不是注解接口。当然可以将@Transactional注解放在接口(或接口方法)上,但这只会在使用基于接口的代理时起作用。Java注解不是从接口继承的这一事实意味着,如果使用基于类的代理(proxy target class=“true”)或基于编织的切面(mode=“aspectj”),那么代理和编织基础设施无法识别事务设置,并且对象也不会包装在事务代理中。
在代理模式下(默认情况下),只有通过代理传入的外部方法调用才会被拦截。这意味着自调用(实际上是目标对象中调用目标对象的另一个方法的方法)在运行时不会导致实际的事务,即使被调用的方法被标记为@Transactional。另外,代理必须完全初始化以提供预期的行为,因此在初始化代码中不应依赖此功能(即@PostConstruct)。
注解驱动的事务说明
- 处理@Transactional注释的默认通知模式是proxy,它只允许通过代理拦截调用。
- proxyTargetClass属性控制为用@transactional注解的类创建的事务代理类型。true则创建基于类的代理。false或省略了该属性,则会创建基于标准JDK接口的代理。
- @EnableTransactionManagement和<tx:annotation-driven/>只在定义了@Transactional的应用程序上下文中的bean上查找@Transactional。这意味着,如果你将注解驱动的配置放在DispatcherServlet的WebApplicationContext中,它只检查控制器中的@Transactional bean,而不是服务中的@Transactional bean。
- 在方法上注解事务优先于在类上注解事务
@Transactional 的配置
@Transactional直接是元数据,它指定接口、类或方法必须具有事务语义。
- value:指定要使用的事务管理器
- propagation:事务转播,枚举类Propagation上定义。
- isolation:隔离级别,枚举Isolation上定义。适用于REQUIRED或REQUIRES\U NEW的传播值。
- timeout:超时时间(秒)。适用于REQUIRED或REQUIRES\U NEW。
- readOnly:是否只读。适用于REQUIRED或REQUIRES\U NEW。
- rollbackFor:对象的数组,必须从Throwable派生。导致回滚的异常类的可选数组。
- rollbackForClassName:类名数组。类必须从Throwable派生。
- noRollbackFor:不导致回滚的异常类的可选数组。
- noRollbackForClassName:不导致回滚的异常类的名称的可选数组。
当前,你无法显式控制事务的名称,其中“name”表示出现在事务监视器(如适用)和日志输出中的事务名称。对于声明性事务,事务名始终是完全限定的类名+.+事务建议类的方法名。例如,如果BusinessService类的handlePayment(..)方法启动了一个事务,则该事务的名称将为:com.example.BusinessService.handlePayment。
public class TransactionalService { @Transactional("order") public void setSomething(String name) { ... } @Transactional("account") public void doSomething() { ... } }
下面显示了bean声明:
<tx:annotation-driven/> <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> ... <qualifier value="order"/> </bean> <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> ... <qualifier value="account"/> </bean>
本例中,TransactionalService上的两个方法在不同的事务管理器下运行,并通过order和account限定符进行区分。如果找不到特定限定的PlatformTransactionManager bean,则仍然使用默认的<tx:annotation driven>目标bean名称transactionManager。
自定义快捷注解
在许多不同的方法上可能会反复使用@Transactional相同的属性,Spring的元注解支持允许你为特定的用例定义自定义快捷注解。例如,考虑以下注解定义:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Transactional("order") public @interface OrderTx { } @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Transactional("account") public @interface AccountTx { }
上一节的示例就会变为:
public class TransactionalService { @OrderTx public void setSomething(String name) { ... } @AccountTx public void doSomething() { ... } }
事务传播机制
本小节描述了Spring中事务传播的一些语义。
在Spring管理的事务中,请注意物理事务和逻辑事务之间的区别,以及传播设置如何应用于这种差异。
使用PROPAGATION_REQUIRED
PROPAGATION_REQUIRED强制执行物理事务,如果当前作用域尚未存在事务,则在本地强制执行物理事务,或者参与为更大范围定义的现有“外部”事务。这是同一线程中常见的调用堆栈安排(例如,委托给多个存储库方法的服务外观,其中所有底层资源都必须参与服务级别事务)。
默认情况下,参与事务加入外部作用域的特征,忽略本地隔离级别、超时值或只读标志(如果有)。如果希望在参与具有不同隔离级别的现有事务时拒绝隔离级别声明,请考虑将事务管理器上的validateExistingTransactions标志切换为true,即尝试参与只读外部作用域的内部读写事务。
PROPAGATION_REQUIRES_NEW
与PROPAGATION_REQUIRED相比,PROPAGATION_REQUIRED始终为每个受影响的事务作用域使用独立的物理事务,从不参与外部作用域的现有事务。在这种安排中,底层资源事务是不同的,因此可以独立地提交或回滚,外部事务不受内部事务回滚状态的影响,内部事务的锁在完成后立即释放。这种独立的内部事务还可以声明自己的隔离级别、超时和只读设置,而不继承外部事务的特性。
了解事务传播的嵌套
PROPAGATION_NESTED使用一个物理事务,它可以回滚到多个保存点。这种部分回滚允许内部事务作用域触发其作用域的回滚,尽管已回滚了一些操作,但外部事务仍能够继续物理事务。此设置通常映射到JDBC保存点,因此它只适用于JDBC资源事务。
事务操作通知
假设你想同时执行事务性操作和一些基本的分析建议。在<tx:annotation-driven/>的上下文中如何实现这一点?
当你调用updateFoo(Foo)方法时,您希望看到以下操作:
- 配置的分析特性启动。
- 执行事务性建议。
- 执行建议对象上的方法。
- 事务提交。
- 分析特性报告整个事务方法调用的确切持续时间。
以下代码显示了前面讨论的简单分析方面:
import org.aspectj.lang.ProceedingJoinPoint; import org.springframework.util.StopWatch; import org.springframework.core.Ordered; public class SimpleProfiler implements Ordered { private int order; // allows us to control the ordering of advice public int getOrder() { return this.order; } public void setOrder(int order) { this.order = order; } // this method is the around advice public Object profile(ProceedingJoinPoint call) throws Throwable { Object returnValue; StopWatch clock = new StopWatch(getClass().getName()); try { clock.start(call.toShortString()); returnValue = call.proceed(); } finally { clock.stop(); System.out.println(clock.prettyPrint()); } return returnValue; } }
通知的顺序是通过有序接口控制的。
以下配置创建一个fooService bean,该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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- this is the aspect --> <bean id="profiler" class="x.y.SimpleProfiler"> <!-- execute before the transactional advice (hence the lower order number) --> <property name="order" value="1"/> </bean> <tx:annotation-driven transaction-manager="txManager" order="200"/> <aop:config> <!-- this advice will execute around the transactional advice --> <aop:aspect id="profilingAspect" ref="profiler"> <aop:pointcut id="serviceMethodWithReturnValue" expression="execution(!void x.y..*Service.*(..))"/> <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/> </aop:aspect> </aop:config> <bean id="dataSource" class="org.apache.commons.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> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
以下示例创建与前两个示例相同的设置,但使用纯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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="fooService" class="x.y.service.DefaultFooService"/> <!-- the profiling advice --> <bean id="profiler" class="x.y.SimpleProfiler"> <!-- execute before the transactional advice (hence the lower order number) --> <property name="order" value="1"/> </bean> <aop:config> <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/> <!-- will execute after the profiling advice (c.f. the order attribute) --> <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/> <!-- order value is higher than the profiling aspect --> <aop:aspect id="profilingAspect" ref="profiler"> <aop:pointcut id="serviceMethodWithReturnValue" expression="execution(!void x.y..*Service.*(..))"/> <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/> </aop:aspect> </aop:config> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- other <bean/> definitions such as a DataSource and a PlatformTransactionManager here --> </beans>
在AspectJ中使用@Transactional
你还可以通过AspectJ切面在Spring容器之外使用Spring框架的@Transactional支持。为此,首先用@Transactional注解你的类(以及可选的类的方法),然后将应用程序与org.springframework.transaction.aspectj.AnnotationTransactionSpect定义在spring-aspects.jar文件。你还必须使用事务管理器配置切面。你可以使用Spring框架的IoC容器来处理依赖注入方面。配置事务管理方面的最简单方法是使用<tx:annotation-driven/>元素并将mode属性指定给aspectj,如在使用@Transactional中所述。因为我们在这里关注的是在Spring容器之外运行的应用程序,所以我们将向你展示如何以编程的方式进行操作。
以下示例显示如何创建事务管理器并配置AnnotationTransactionSpect以使用它:
// construct an appropriate transaction manager DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource()); // configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
当你使用此切面时,必须注解实现类(或该类中的方法或两者),而不是该类实现的接口(如果有的话)。AspectJ遵循Java的规则,即接口上的注解不被继承。
类上的@Transactional注解为类中任何公共方法的执行指定了默认事务语义。
类内某个方法上的@Transactional注解覆盖了类注解(如果存在)给出的默认事务语义。你可以注解任何方法,而不管可见性如何(即不管是private还是public)。
要使用annotationTransactionSpect编织应用程序,必须使用AspectJ构建应用程序或使用加载时编织。
五、编程式事务管理
Spring框架提供了两种编程事务管理方法,使用:
- TransactionTemplate
- 直接实现PlatformTransactionManager
Spring团队通常建议使用TransactionTemplate进行编程事务管理。第二种方法类似于使用JTA UserTransaction API,不过异常处理不那么麻烦。
使用TransactionTemplate
TransactionTemplate采用与其他Spring模板(如JdbcTemplate)相同的方法。它使用回调方法(将应用程序代码从样板获取和释放事务资源中解放出来)并产生意图驱动的代码,因为代码只关注您想要做的事情。
正如下面的示例所示,使用TransactionTemplate绝对会将你与Spring的事务基础设施和api联系在一起。编程事务管理是否适合你的开发需求,你必须自己做出的决定。
作为应用程序开发人员,你可以编写TransactionCallback实现(通常表示为匿名内部类),其中包含需要在事务上下文中执行的代码。然后,可以将自定义TransactionCallback的实例传递给TransactionTemplate上公开的execute(..)方法。下面的示例演示如何执行此操作:
public class SimpleService implements Service { // single TransactionTemplate shared amongst all methods in this instance private final TransactionTemplate transactionTemplate; // use constructor-injection to supply the PlatformTransactionManager public SimpleService(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } public Object someServiceMethod() { return transactionTemplate.execute(new TransactionCallback() { // the code in this method executes in a transactional context public Object doInTransaction(TransactionStatus status) { updateOperation1(); return resultOfUpdateOperation2(); } }); } }
如果没有返回值,则可以将TransactionCallbackWithoutResult类与匿名类一起使用,如下所示:
transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { updateOperation1(); updateOperation2(); } });
回调中的代码可以通过调用所提供的TransactionStatus对象上的setRollbackOnly()方法回滚事务,如下所示:
transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { try { updateOperation1(); updateOperation2(); } catch (SomeBusinessException ex) { status.setRollbackOnly(); } } });
指定事务设置
你可以通过编程方式或在配置中指定事务设置(如传播模式、隔离级别、超时等等)。默认情况下,TransactionTemplate实例具有默认的事务设置。以下示例显示特定TransactionTemplate的事务设置的编程自定义:
public class SimpleService implements Service { private final TransactionTemplate transactionTemplate; public SimpleService(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); // the transaction settings can be set here explicitly if so desired this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); this.transactionTemplate.setTimeout(30); // 30 seconds // and so forth... } }
以下示例通过使用Spring XML配置定义具有某些自定义事务设置的TransactionTemplate:
<bean id="sharedTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/> <property name="timeout" value="30"/> </bean>"
然后,你可以将sharedTransactionTemplate注入所需的服务中。
最后,TransactionTemplate类的实例是线程安全的,因为实例不维护任何会话状态。但是,TransactionTemplate实例会维护配置状态。因此,虽然许多类可能共享TransactionTemplate的单个实例,但如果类需要使用具有不同设置的TransactionTemplate(例如,不同的隔离级别),则需要创建两个不同的TransactionTemplate实例。
使用PlatformTransactionManager
你也可以使用org.springframework.transaction.PlatformTransactionManager直接管理你的事务。为此,请通过bean引用将PlatformTransactionManager的实现传递给bean。然后,通过使用TransactionDefinition和TransactionStatus对象,可以启动事务、回滚和提交。下面的示例演示如何执行此操作:
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); // explicitly setting the transaction name is something that can be done only programmatically def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { // execute your business logic here } catch (MyException ex) { txManager.rollback(status); throw ex; } txManager.commit(status);
六、在编程事务管理和声明性事务管理之间进行选择
当你有少量的事务操作时,编程事务管理通常是一个好主意。例如,如果你有一个web应用程序只需要某些更新操作的事务,那么你可能不希望使用Spring或任何其他技术来设置事务代理。在这种情况下,使用TransactionTemplate可能是一个好方法。能够显式地设置事务名称,通过使用事务管理的编程方法来实现的。
另一方面,如果你的应用程序有许多事务操作,声明性事务管理通常是值得的。它将事务管理排除在业务逻辑之外,并且不难配置。当使用Spring框架而不是EJB-CMT时,声明性事务管理的配置成本大大降低。
七、事务绑定事件
从Spring4.2开始,事件的监听器可以绑定到事务的某个阶段。典型的例子是在事务成功完成时处理事件。当当前事务的结果对监听器非常重要时,这样做可以更灵活地使用事件。
你可以使用@EventListener注解注册常规事件监听器。如果需要将其绑定到事务,请使用@TransactionalEventListener。当年你这样做时,默认情况下监听器绑定到事务的提交阶段。
下一个例子展示了这个概念。假设一个组件发布一个order created事件,并且我们希望定义一个监听器,该监听器只应在成功提交已发布的事务后处理该事件。以下示例设置这样的事件监听器:
@Component public class MyComponent { @TransactionalEventListener public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) { ... } }
@TransactionalEventListener注解公开了一个阶段属性,该属性允许你自定义监听器应绑定到的事务的阶段。有效的阶段是在提交之前(BEFORE_COMMIT)、提交之后(AFTER_COMMIT ,默认)、回滚之后(AFTER_ROLLBACK)以及事务完成(无论是提交还是回滚)之后。
如果没有事务正在运行,则根本不会调用监听器,但是,可以通过将注解的fallbackExecution 属性设置为true来覆盖该行为。
八、与特殊应用程序服务器的集成
Spring的事务抽象通常与应用服务器无关。
Spring的JtaTransactionManager是运行在java EE应用服务器上的标准选择,众所周知,它可以在所有常用服务器上运行。拥有很多高级功能,如事务挂起,也可以在许多服务器上工作(包括GlassFish、JBoss和Geronimo),而不需要任何特殊的配置。然而,对于完全支持的事务挂起和进一步的高级集成,Spring包括用于WebLogic 和WebSphere的特殊适配器。
对于标准场景,包括WebLogic 和WebSphere,考虑使用方便的<tx:jta-transaction-manager/>配置元素。配置后,此元素将自动检测底层服务器,并选择平台可用的最佳事务管理器。这意味着你不需要显式地配置特定于服务器的适配器类。相反,它们是自动选择的,标准JtaTransactionManager作为默认的后备。
IBM WebSphere
在WebSphere6.1.0.9及更高版本上,建议使用的SpringJTA事务管理器是WebSphereRowTransactionManager。这个特殊的适配器使用IBM的UOWManager API,它在websphereapplicationserver6.1.0.9和更高版本中提供。有了这个适配器,IBM正式支持Spring驱动的事务挂起(由PROPAGATION_REQUIRES_NEW启动的挂起和恢复)。
WebLogic
在WebLogic9.0或更高版本上,你通常会使用WebLogicJtaTransactionManager,而不是常用的JtaTransactionManager类。这个特殊的JtaTransactionManager特定于WebLogic的子类支持在WebLogic管理的事务环境中Spring的事务定义的全部功能,而不仅仅是标准的JTA语义。包括事务名称、每个事务的隔离级别以及在所有情况下正确地恢复事务。