Spring 事务管理

翻译自spring/docs/4.2.x

翻译过程中使用工具:google翻译,欧路词典

名词解释:
JTA:Java Transaction API
JPA:Java Persistence API
JDO:Java Data Object
CMT: Container Managed Transactions
JNDI:Java Naming and Directory Interface
JMS: Java Message Service
JCA: Java EE Connector Architecture

1. 事务管理

1.1 Spring框架事务管理介绍

全方面的事务支持是使用Spring框架的最引人注目的原因之一。Spring框架为事务管理提供一致性抽象提供了如下好处:

  • 一致性编程模型横跨不同的事务APIs,例如 JTA,JPA,JDO。
  • 支持声明式事务管理
  • 和复杂编程事务APIs(例如JTA)相比,Spring提供更简单的编程事务管理的API。
  • 与Spring的数据访问抽象有良好的集成。

下面的部分描述Spring框架的事务value-adds和技术。(这部分也包含对于最好事务管理的实践,应用服务器集成和常见问题解决方案的讨论)

1.2 Spring框架事务支持模型的优势

传统上,Java EE的开发者对于事务管理有两种选择:全局或者本地事务管理,这两种都有很大的局限性。全局和本地事务管理将在下面的两小节中回顾,接着是Spring框架管理支持解决全局和本地事务管理模型局限性的讨论。

1.2.1 全局事务

全部事务管理允许你和多个事务资源一块运行,事务资源一般是关系型数据库和消息队列。应用服务器通过JTA管理全局事务,JTA是一个用起来笨重的API(部分是由于它的异常模型决定的)。此外,JTAUserTransaction正常来说需要JNDI引入资源,意味着为了使用JTA你还需要使用JNDI。显然,全局事务的使用将会限制应用代码潜在的复用,同时JTA只能在应用服务器环境中可用。
先前,使用全局事务比较好的方式是通过EJB的容器管理事务:CMT是一张声明式事务管理表(对于编程事务管理也同样好用)。EJB CMT删除了关联事务JNDI的查找,尽管EJB自己必须要使用JNDI。它消除了编写Java代码以控制事务的大部分但不是全部的需要。CMT最大的缺陷是捆绑JTA和应用的服务器环境。同时,CMT只有在EJBs里实现业务逻辑才可用,或者至少在一个事务EJB facade之前。通常EJB的负面影响太大以致于这不是一个有吸引力的方案,尤其是面对声明式事务管理的引人注目的备选方案。

1.2.2 本地事务

本地事务是特定于资源的,比如一个事务关联一个JDBC连接。本地事务可能使用简单,但是有明显的缺陷:它们不能在多个事务资源上工作。举个例子,使用JDBC连接的管理事务代码不能在全局JTA事务中运行。因为应用服务器不参与事务管理,它不能确保跨多个数据源的正确性。(值得注意的是,大多数应用使用单个事务资源)另一个缺陷是本地事务在编程模型中是侵入式的。

1.2.3 Spring框架的一致性编程模型

Spring 解决了全局和本地事务的缺陷。它允许程序员在任何环境中使用一致性编程模型。你写一次代码,它可以在不同环境中的不同事务管理策略中受益。Spring框架提供声明和编程事务管理。大多数用户使用在大多数情况下被推荐使用的声明式事务模型。
对于编程式事务管理,开发者将使用Spring 框架的事务抽象,它可以在底层的事务基础设施上运行。对于声明式模型,开发经常写一点或者没有代码去关联事务管理,因此不依赖Spring框架的事务API或者其他的事务API。

对于事务管理,你是否需要应用服务器?
Spring 框架事务管理支持当企业Java应用需要应用服务器时修改传统规则。
尤其,你只是需要通过EJBs声明事务而不是需要应用服务器。事实上,即使你的应用服务器有的JTA能力,对比EJB CMT,你可能决定使用Spring框架的声明式事务提供更多能力和一个富有成效的编程模型。
通常你需要使用应用服务器的JTA能力仅仅因为你的应用需要跨多个资源去处理事务,这种情况对于许多应用是不需要的,许多高端应用使用一个高扩展性的数据库(如:Oracle RAC)代替多个资源。独立事务管理如 Atomikos Transactions 和 JOTM是其他选择。当然,你可能需要其他应用服务器能力,如Java消息服务和JCA。
Spring框架给你当需要扩展你的应用去完整加载应用服务器的选择,替代使用EJB CMT或者JTA的唯一方式是使用本地事务(如:JDBC连接)写代码的日子一去不复返了,如果你需要在全局或者CMT中运行该本地事务代码将会面对大量的返工。使用Spring框架,你仅仅需要在配置文件中定义一些bean,而不是需要去修改代码。

1.3 理解Spring框架事务抽象

spring框架事务抽象的关键是事务策略的概念,通过org.springframework.transaction.PlatformTransactionManager接口定义事务策略。

1
2
3
4
5
public interface PlatformTransactionManager{
TransactionStatus getTransaction(TransactionDefinition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}

这个主要是Java的服务提供发现机制,尽管可以从应用代码中以编程方式使用。因为PlatformTransactionManager是一个接口,它可以在被需要是很容易的仿制或者存根。它和类似JNDI的查找策略无关。
定义PlatformTransactionManager的接口实现和spring IoC容器中的其他对象(或bean)相同。即使你使用JTA,仅此一项好处也使得Sping框架事务值得被抽象。相比于直接使用JTA,使用Spring事务抽象可以让测试事务代码更简单。
可以被任何PlatformTransactionManager接口的实现方法抛出的非检查(继承java.lang.RuntimeException的类)的TransactionException再次符合Spring的理念。事务基础设施的故障几乎总是致命的。应用代码在极少数情况下可以从事务失败中恢复,应用开发者可以选择捕获和处理TransactionException。重点是开发者不必强制去这样做。
getTransaction(..)方法返回TransactionStatus对象,依赖一个TransactionDefinition参数。返回的Transaction可能代表一个新事务,或者可以代表一个已经存在的事务如果此事务在当前调用栈中存在匹配的事务。后者的意义是在Java EE事务上下文中,一个线程的执行和一个TransactionStatus相关联。TransactionDefinition接口指定:

  • Isolation: 当前事务和其他事务在工作中的隔离程度。例如: 此事务是否可以看到来自其他事务的未提交的写入?
  • Propagation: 通常,在事务范围内运行的所有代码都将在此事务中运行。但是,你可以选择在事务上下文已经存在且执行事务方法的事件中指定行为。例如:代码可以在已存在的事务中继续运行(常见情况);或者挂起已存在的事务,新建事务。Spring提供了EJB CMT中熟悉的所有事务传播选择。了解Spring中关于事务传播语义,事务传播
  • Timeout: 在超时和底层事务基础设施自动回滚事务之前,事务可以运行多久?
  • Read-only status: 当你的代码读取但是不修改数据数据时可以使用只读事务。只读事务在某些情况下是有用的优化,例如当你使用Hibernate时。

这些事务反映出了标准的事务概念,如果需要,请参阅讨论事务隔离等级和其他核心事务概念的资源。理解这些概念对使用Spring框架或者其他事务管理的解决方案是很必要的。

TransactionStatus接口提供简单的事务代码去控制事务执行和查询事务状态。这些概念应该是熟悉的,它们对所有事务APIs都是通用的。

1
2
3
4
5
6
7
8
9
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。)
定义一个JDBCDataSource

1
2
3
4
5
6
7

<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定义将有一个DataSource定义的引用。它看起来像这样:

1
2
3
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">
</bean>

如果你在Java EE容器中使用JTA,你将使用通过容器从JNDI中获取的DataSource,和Spring的JtaTransactionManager相关联。这就是JTA和JNDI查找版本的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
http://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不需要知道DataSource或者其他指定的资源,因为它使用容器的全局事务管理基础设施。

以上定义的dataSourcebean使用jee命名空间的<jdni-lookup/>标签。对于schema-based的更多配置信息,Chapter40, XML Schema-based 配置,对于<jee/>标签的更多信息,请看Section 40.2.3

你也可以使用轻松的使用Hibernate本地事务,正如在下面展示的例子。这种情况,你需要定义一个HibernateLocalSessionFactoryBean,你的代码将使用它去获取HibernateSession实例。

DataSourcebean定义和之前示例本地JDBC例子相似,所以下面不在展示。

如果DataSource被任何非JTA事务管理使用,将通过JNDI查找,Java EE容器管理,然后它将是非事务性的,因为是使用Spring框架管理事务,而不是Java EE容器。

txManagerbean 在这种情况下是属于HibernateTransactionManager类型。以相同的方式,正如DataSourceTransactionManager需要引用DataSourceHiberanteTransactionManager需要引用SessionFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<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

1
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

如果你使用JTA,你的事务管理定义将看起来一样,而不管你用了什么数据访问技术,是JDBC,Hibernate JPA或者其他支持的技术。这个是确定的事实,JTA 事务是全局事务,可以加入任何的事务资源。

对于以上所有情况,应用代码不需要 改变。你可以通过改变配置来改变如何管理事务,即使你从本地事务改为全局事务,反之亦然。

1.4 将资源与事务同步

现在你应该很清楚如何创建不同的事务管理,和它们怎样和需要被同步事务相关资源链接(例如:DataSourceTransactionManager链接JDBCDataSourceHibernateTransactionManager链接HibernateSessionFactory,等等。)这个章节描述应用代码怎样直接或者非直接使用持久API,如JDBC,Hibernate,或者JDO,确保这些资源被创建,复用,和正确的清理。这个章节也讨论事务同步怎样通过相应的PlatformTransactionManager触发。

1.4.1 高级同步方式

首选方法是使用Spring基于高级模板的持久性集成APIs或者使用本地ORM APIs与transacton-aware工厂bean或者代理一起使用,来管理本地资源工厂。transaction-aware解决方案在内部处理资源的创建,复用,清理,资源的可选事务同步和异常映射。因此用户数据访问代码不必解决这些任务,但是可以完全关注于非样板持久逻辑。通常,你使用本地ORM API或者使用模版方式通过使用JdbcTemplate进行JDBC访问。这些解决方案在本参考文档的随后章节中有详细说明。

1.4.2 低级同步方式

类例如:DataSourceUtils(JDBC),EntityManagerFactoryUtils(JPA),SessionFactoryUtils(Hibernate),PersistenceManagerFactoryUtils(JDO),等类存在于较低级别。
本地持久化APIs,你使用这些类去确保Spring框架管理的实例被正确获取,事务被同步(可选),在进程中发生的异常正确的被映射到一致的API。
例如,在JDBC的情况下,而不是在DataSource调用getConnection()方法的传统的JDBC方式,你替换使用Spring的org.springframework.jdbc.datasource.DataSourceUtils类,如下:

1
Connection conn = DataSourceUtils.getConnection(dataSource);

如果一个已经存在的事务有一个链接和它同步,这个实例将被返回。否则,这个方法调用出方法新链接的创建,这个链接(可选)被同步到已经任何已存在的事务上,使得在随后的同一事务中重复调用。 如上所述,任何SQLException在Spring框架CannotGetJdbcConnectionException中是被包括的,是Spring框架非校验数据访问异常之一。这个方式提供了比你从SQLException中轻松获的更多的信息,确保跨数据库的可移植性,设置跨不同的持久化技术。

这种方式运行也没有Spring事务管理(事务同步时可选的),所以无论你是否使用Spring事务管理,你都可以使用它。

当然,一旦你使用了Spring的JDBC支持,JPA支持或者Hibernate支持,你通常更喜欢不使用DataSourceUtils或者其他的类,因为相比于直接使用相关的APIs,通过Spring抽象你将工作的更快乐。例如:你使用SpringJdbcTemplate或者jdbc.object包去简化你使用的JDBC,在幕后发生的链接恢复,你将不在需要写任何特定代码。

1.4.3 TransactionAwareDataSourceProxy

TransactionAwareDataSourceProxy类作为一个低级存在。这是一个DataSource的代理,它包装目标DataSource以增加对Spring管理的事务的认识。在这个方面,它类似于通过Java EE服务器提供的事务JNDIDataSource

使用这个累根本不需要或者不可取,除非当已存在的代码必须被调用和传递了一个标准JDBCDataSource接口实现。在这种情况下,代码可能有用,但是参与Spring的事务管理。最好是通过使用上面提到的高级抽象去写新代码。

1.5 声明式事务管理

大多数Spring框架用户选择声明式事务管理。此选项对应用程序代码的影响最小,因此最符合非侵入式轻量级容器的理想。

使用Spring切面编程的Spring 框架的声明式事务管理成为可能。但是,由于事务切面代码随Spring Framework发行版一起提供并且可能以样板方式使用,因此通常没必要理解AOP概念来有效地使用此代码。

Spring 框架的声明式事务管理和 EJB CMT在给单个方法级别指定(缺少)事务的行为是相似的。在需要的情况下,不使用事务上下文调用setRollbackOnly()是可能的。两种事务管理类型的不同点是:

  • 和 EJB CMT不一样,绑定JTA,Spring框架的声明式事务管理可以在任何环境中运行。也可以通过调整配置文件使用JTA事务或者使用JDBC,JPA,Hibernate或者JDO本地事务。
  • 你可以对任意类用Spring框架声明式事务管理,不仅仅是对于像EJBs的特殊类。
  • Spring框架提供声明式回滚规则,没有和EJB等效的功能。提供编程式和声明式两种回滚规则支持。
  • Spring框架允许通过使用AOP自定义事务行为。例如,你可以在事务回滚的情况下插入自定义行为。你随着事务advice也可以添加任意advice。使用EJB CMT,除了setRollbackOnly你无法影响容器的事务管理。
  • Spring框架和高端服务器一样不支持跨远程调用事务上下文传播特性。如果你需要使用这种特性,我们建议你使用EJB。但是,使用这种特性之前考略清楚,因为一般来说,不想支持事务的跨远程调用。

TransactionProxyFactoryBean在哪?
在Sping的2.0版本及以上版本的声明事务配置和以前的Spring版本有很大的区别。主要的不同是不在需要配置TransactionProxyFactoryBeanbeans。
Spring2.0版本之前的配置依然是100%有效的配置;将新<tx:tags/>视为代表你简单的定义TransactionProxyFactoryBean beans。

回滚规则的概念是重要的:它们允许你头指定哪种异常(或者抛出)应该造成自动的回滚。你以声明的方式指定它,在配置中,不在Java代码中。因此,尽管你可以始终在TransactionStatus对象上调用setRollbackOnly方法去回滚当前事务,但是大多数你常常可以指定MyApplicationException必须总是造成回滚的规则。这个选择对的重大优势是业务对象不依赖事务基础设施。例如,它们通常需要导入Spring 事务APIs或者其他Spring APIs。

尽管EJB容器默认行为在系统异常(通常是运行时异常)中自动回滚事务,EJB CMT在应用异常(除java.rmi.RemoteException外的检查时异常)中不自动回滚事务。虽然Spring声明式事务管理的默认行为遵循EJB约定(仅仅在非检查异常时自动回滚),但是,自定义这个行为是经常有用的。

1.5.1 理解声明式事务管理实现

简单的告诉你去使用@Transactional注解去注解你的类,在配置中添加@EnableTransactionMangement,然后期待你理解它全部怎样运行的是不足够的。这个章节解释在发生与事务相关的问题时Spring的声明式事务基础设施内部工作原理。
关于Spring框架的说明书事务支持最重要的概念是通过AOP代理启用这个支持,事务advice由元数据(目前基于XML或者注解)驱动。事务元数据和AOP的组合生成一个代理,这个代理使用TransactionInterceptor和适当的PlatfromTransactionManager实现来驱动围绕方法调用的事务。

Spring AOP 包含在Chapter 10, Aspect Oriented Programming with Spring.

从概念上讲,调用在一个事务代理上的一个方法像这样:

1.5.2 声明式事务实现的例子

考虑下面的接口,和随后的实现。这个例子使用FooBar类作为标志符,以便于你可以不用管不住特殊域模型,只集中关注事务的使用。对于这个例子的目的,在每一个实现方法体内,DefaultFooService类抛出UnsupportedOperationException实例的事实是好的;它允许你查看创建的事务和然后回滚来响应UnsupportedOperationException实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

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);

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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();
}

}

假设FooService接口的前面两个方法,getFoo(String)getFoo(String, String),必须在一个带有只读语义事务的上下文中执行,和其他的方法,insertFoo(Foo)updateFoo(Foo),必须在带有只读语义事务上下文中执行。如下配置在下面的几段中被详细解释。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<?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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://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>

检查前面的配置。你想创建一个服务对象,fooServicebean。要应用的事务语义被封装在<tx:advice/>定义中。<tx:advice/>定义读作“在只读事务上下文中所有要执行的方法以get开头,所有其他要执行的方法带有默认事务语义。“<tx:advice/>标签的transaction-manager属性被设置为将要驱动事务的PlatformTransactionManagerbean 的name,在当前的配置文件中,是txManagerbean。

如果你想设置PlatformTransactionManagerbean的name为transactionManager,你可以忽略事务advice的transaction-manager属性。如果你想使用任何其他的name给PlatformTransactionManagerbean,正如在之前的例子中,你之后必须明确的使用transaction-manager属性。

<aop:config/>的定义确保在程序中适当的点执行通过txAdvicebean定义的事务advice。首先你定义一个切入点,切入点与在FooService接口(fooServiceOperation)中定义的任何操作执行的相匹配。然后使用advisor关联切入点和txAdvice。结果表明在执行fooServiceOperation时,通过txAdvice定义的advice将会运行。

<aop:pointcut/>元素中定义的表达式,是一个AspectJ切入点表达式;查看Chapter 10, Aspect Oriented Programming with Spring了解更多有关Spring中的切入点表达式的详细信息。

一个常见的需求时创建一个全部服务层的事务。实现这种需求的最好方式是只修改切入点表达式去匹配在你服务层的所有操作。例如:

1
2
3
4
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>

在这个例子中:它假设你所有服务接口在x.y.service包中定义;查看Chapter 10, Aspect Oriented Programming with Spring 了解更多信息。

现在,我们已经分析了配置,你可能会问你自己,“这所有的配置实际上做了什么?”。

以上配置将被用来去创建一个事务代理围绕从fooServicebean 定义创建的对象。这个代理将使用事务advice配置,以便于在代理上调用一个适当的方法时,一个事务开始,挂起,被标记为只读等等,取决于与该方法关联的事务配置。考虑一下测试驱动以上配置的代码:

1
2
3
4
5
6
7
8
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的堆栈跟踪已被截断。)

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
27

<!-- 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)

1.5.3 回滚一个声明式事务

上面的章节概述了如何在你的应用中给类(通常是服务层类)声明式的指定事务设置。这个章节描述如果以一种简单的声明式方式控制事务回滚。
向Spring框架的事务基础设施表明回滚一个事务的推荐的方式是在当前一个事务上下文从执行的代码中抛出Exception。Spring框架事务基础设施代码将捕获所有未处理异常,因为它会调用堆栈,做出判定是否标记回滚事务标识。

在它的默认配置中,Spring框架的事务基础设施代码仅仅在runtime,unchecked异常时标记回滚事务;也就是说,当抛出的异常是一个实例或者RuntimeException的子类。(Errors在默认配置下也会回滚)。在一个事务方法中抛出的已检查异常在默认配置下不会造成回滚。

你可以准确的配置哪些类型的异常标记事务回滚,包括已检查的异常。下面的XML片段演示你怎样给已检查和应用指定异常类型配置回滚。

1
2
3
4
5
6
<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>

如果当抛出一个异常的时候,你不想事务回滚,你也可以指定‘不回滚规则’。下面的列子告诉你Spring框架的事务基础设施即使面对未处理的InstrumentNotFoundException也要提交伴随事务。

1
2
3
4
5
6
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>

当Spring框架的事务基础设施缓存一个异常,参考回滚配置规则来决定是否标记回滚事务时,强匹配规则胜利。因此,在下面配置的这种情况,除InstrumentNotFoundException以外的所有异常都会造成伴随事务的回滚。

1
2
3
4
5
6

<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
</tx:attributes>
</tx:advice>

你可以表示一个需要编程式回滚。尽管非常简单,这个进程时侵入式的,Spring框架的事务基础设施将紧紧侵入你的代码。

1
2
3
4
5
6
7
8
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}

如果有可能强烈推荐使用声明式途径回滚事务。编程式事务回滚应该在你绝对需要时使用,但是它的用法在实现基于POJO的清洁框架时很明显。

1.5.4 给不同的bean配置不同的事务语义

考虑具有多个服务层对象的情况,并且您希望对每个对象应用完全不同的事务配置。使用不同的切入点和advice-ref属性值定义不同的<aop:advisor />元素。作为比较,首先假设您的所有服务层类都在根x.y.service包中定义。要使所有作为在该包(或子包中)中定义的类的实例的bean以及以Service结尾的名称具有默认的事务配置,您将编写以下内容:

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

<?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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://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。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

<?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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://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>

1.5.5 <tx:advice/>设置

这个章节总结可以指定使用<tx:advice/>标签的各种事务配置。默认的tx:advice/`设置:

  • 传播特性是 REQUIRED
  • 隔离等级是 DEFAULT
  • 事务是读/写
  • 事务超时默认为底层事务系统的默认超时,如果不支持超时则为none
  • 任意RuntimeException触发回滚,任意的检查时异常不回滚。

你可以改变这些默认设置;嵌套在<tx:advice/><tx:attributes/>标签中的<tx:method/>标签的各种属性总结如下:
tx:method/ 设置:

1.5.6 使用@Transactional

除了基于XML的方式声明事务配置,你也可以使用基于注解的方式。直接在Java源代码中声明事务语义使声明更接近被作用的代码。没有太多过度耦合的危险,因为无论如何,以事务方式使用的代码几乎总是以这种方式部署。

标准javax.transaction.Transactional注解支持使用Spring自己的注解直接替换。请参考JTA 1.2 文档了解更多详细资料。

使用@Transactional注释所提供的易用性最好通过一个示例来说明,该示例将在后面的文本中进行说明。考虑以下类定义:

1
2
3
4
5
6
7
8
9
10
11
12
// 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);
}

当上面的POJO被定义为Spring IoC容器中的bean时,可以通过仅添加一行XML配置来使bean实例成为事务性的。

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
27
28

<!-- 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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://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>

如果要连接的PlatformTransactionManager的bean的名字是transactionManager,你可以忽略<tx:annotation-driven/>的transaction-manager属性。如果你要依赖注入的PlatformTransactionManagerbean有任何其他的名字,你必须和上面的例子一样明确的使用transaction-manager
如果你基于Java配置,@EnableTransactionManagement注解提供有效的支持。只需添加@Configuration注解类。查看全部详细内容请看javadocs。

方法可见性和@Transaction
当使用代理时,仅仅对于public可见性的方法应用@Transactional注解。如果你对protected,private或者包内可见的方法使用@Transactional注解,虽然没有错误,但是这个已经注解的方法不展示已配置的事务配置。如果你需要直接非public的方法,可以考虑使用AspectJ(见下文)。

你可以在一个接口定义,接口上的方法,类定义或者类上的public方法前面设置@Transactional。但是,只有@Transactional注解是不足以激活事务行为的。@Transactional注解是一个简单的元数据,它可以被一些运行时的基础设施消费,这个基础设施是@Transactional-aware和可以使用元数据配置具有事务行为的适当的bean。在上面的例子中,<tx:annotation-driven/>元素打开事务行为。

Spring 推荐你只使用@Transactional注解具体类(和具体类的方法),而不是注解接口。你通常可以在一个接口(或者一个接口的方法)上配置@Transactional注解,但是,仅仅在你使用基于接口代理的方式时才能获得你期望的运行。Java注解不从接口继承Java注解的事实意味着如果你使用基于类的代理(proxy-target-class="true")或者基于切面织入(mode=""aspectj),然后代理和织入的基础设施将不识别事务设置,并且这个对象不被包裹在事务代理中,这将是非常糟糕的。

在代理模式(默认)中,仅外部的方法通过代理调用进入会被拦截。这意味着自我调用,实际上,在目标对象的方法调用目标对象的其他方法将不会在运行时引起一个实际的事务,即使被调用的方法使用@Transactional标记。这个代理也必须完整的初始化去提供你期望的行为,因此,你不应该在你的初始化代码中依赖这个特性,即@PostConstruct

如果你希望自我调用也被包裹在事务中,可以考虑切面模式(在下面的表格中查看模式的属性)的使用。在这种情况下,首先不会有一个代理;相反,为了将@Transactional转换为任何类型方法上的运行时行为,目标类将被织入(它的字节码将被修改)。

XML AttributeAnnotation AttributeDefaultDescription
transaction-mangerN/A(查看TransactionManagementConfigurer javadocs)transactionManager要使用的事务管理器的名称。仅在如果事务管理器的名称不是transactionManager时需要,如上面的例子所示
modemodeproxy默认模式“代理”进程使用Spring的AOP框架(以下代理语义,正如上面讨论的,仅适用于通过代理进入的方法调用)代理被注解的bean。替代模式“aspectj”代替使用Spring的AspectJ事务切面织入受影响的类,修改目标类字节码以应用任何类型的方法调用。AspectJ 织入需要spring-aspects.jar在classpath中,同时加载时织入(编译时织入)开启。(查看怎样设置加载时织入的详细内容,请访问Spring配置
proxy-target-classproxyTargetClassfalse仅适用代理模式。控制给带有@Transactional注解的注解类创建什么类型的事务代理。如果proxy-target-class属性设置为true,基于类的代理将被创建。如果proxy-target-class为false或者这个属性被忽略,标准JDK基于接口的代理将被创建。(详细检查不同类型代理请查看10.6 代理机制。)不指定ordering意味着AOP子系统决定advice的order

@EnableTransactionManagement<tx:annotation-driven/>只查找它们定义在相同应用上下文bean上的@Transactional。这意味着,如果你在一个WebApplicationContext中给一个DispatcherServlet添加注解驱动配置,它仅仅检查你controller带有@Transactional的bean,而不是你的service。了解更多信息查看The DispatcherServlet

在评估方法的事务设置时,派生最多的位置优先。下面列子的这种情况,DefaultFooService类在类级别中使用只读事务设置注解,但是,在相同的类中,在updateFoo(Foo)方法上的@Transactional注解优先于类级别定义的事务设置。

@Transactional 设置
@Transactional注解是一个元数据,这个元数据指定一个接口,类或者方法必须有事务语义;例如,“当方法被调用时,开始一个全新的只读事务,中止任何已存在的事务“。默认的@Transactional设置如下:

  • 传播特性设置是PROPAGATION_REQUIRED
  • 隔离等级是ISOLATION_DEFAULT
  • 事务是读/写。
  • 事务超时默认是底层事务系统的默认超时,或者如果不支持超时为none。
  • 任何RuntimeException触发回滚,任何已校验异常不会。

这些默认设置可以改变;在下表中总结了@Transactional注解的各种属性:

PropertyTypeDescription
valueString可选限定符,指定要使用的事务管理器
propagationenum:Propagation可选传播特性设置
isolationenum:Isolation可选隔离级别
readOnlyboolean读/写 vs 只读
timeoutint(秒粒度)事务超时
rollbackFor类的对象数组,必须从Throwable派生可选异常类数组,这些异常必须造成回滚
rollbackForClassName类的名称数组,类必须从Throwable派生可选异常类名称数组,必须造成回滚
noRollbackFor类的对象数组,必须从Throwable派生可选异常类数组,这些异常必须不造成回滚
noRollbackForClassName类的名称数组,类必须从Throwable派生可选异常类名称数组,必须不造成回滚

目前,你无法明确的掌控事务的名称,如果适用,其中name表示将在事务监视器中显示的事务名称,以及日志记录输出。对于声明式事务,事务名总是全量类名+“.”+事务的advised类的方法名。例如,如果BusinessService类的handlePayment(...)方法开始一个事务,事务名称将会是com.foo.BusinessService.handlePayment

使用@Transactional的多个事务管理

大多数Spring应用仅仅需要单个事务管理,但是可能也会有在单个应用中你想多个独立事务管理的情况。@Transactional注解的属性值可以被用来选择性指定要使用的PlatformTransactionManager。这可以是bean的名称或者事务管理bean的值。例如,如下Java代码使用的限定符值。

1
2
3
4
5
6
7
8
9

public class TransactionalService{

@Transactional("order")
public void setSomething(String name){...}

@Transactional("account")
public void doSomething(){...}
}

可以在应用上下文中和如下的事务管理bean声明组合

1
2
3
4
5
6
7
8
9
10
11
12

<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’s meta-annotaion support]允许你在你的指定使用情况下定义自定义快捷方式的注解。例如,如下注解的定义:

1
2
3
4
5
6
7
8
9
10
11
12

@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 {
}

允许我们将上一小节的例子写为如下:

1
2
3
4
5
6
7
8
9
10

public class TransactionalService{

@OrderTx
public void setSomething(String name){...}

@AccountTx
public void doSomething(){...}

}

这里我们使用了定义事务管理器的限定符语法,但是我们也可以包含传播特性行为,回滚规则,超时等等。

1.5.7 事务传播特性

这个章节描述在Spring中的事务传统特性的一些语义。请注意这个章节不是适当的一个事务传播特性介绍;二是详细描述在Spring中关于事务传播特性的一些语义。

在Spring管理的事务中,注意物理和逻辑事务的区别,和事务传播特性设置如何应用此差异。

PROPAGATION_REQUIRES_NEW,与PROPAGATION_REQUIRED相比,为每一个事务作用域使用一个独立的事务。在那种情况下,底层的物理事务是不同的,因此可以独立的提交或者回滚,外部事务不受内部事务回滚状态的影响。

Nested

PROPAGATION_NESTED使用具有多个保存点的单个物理事务,它可以回滚到该事务。一些局部是回滚允许内部事务域在它的作用域触发回滚,使用外部事务可以继续物理事务而不用管一些已经回滚的操作。这个设置常用来映射JDBC的保存点。因此仅仅和JDBC资源事务一起工作。查看Spring的DataSourceTransactionManager

1.5.8 Advising 事务操作

假如你想同时执行事务和一些基础剖析advice。你怎样在<tx:annotaion-driven/>的上下文中实现这点?

当你调用updateFoo(Foo)方法,你想看到如下动作:

  • 配置的剖析切面启动
  • 事务advice执行
  • advised对象上的方法执行
  • 事务提交
  • 剖析切面精确报告整个事务方法调用期间

这个章节不关心详细介绍AOP(除非AOP适用于事务)。有关以下AOP配置和AOP的详细介绍,请参见Chapter 10,Aspect Oriented Programming with Spring

这是上面讨论简单剖析切面的代码。advice的排序由Ordered接口控制。了解advice 排序的全部详细内容,查看the section called “Advice ordering”..

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
27
28
29
30
31
32
33
package x.y;

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;
}
}
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://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>

上述配置的结果是一个fooService bean,它具有按所需顺序应用于它的分析和事务切面。你可以以类似的方式配置任意数量的其他切面。

以下示例实现与上述相同的配置,但是使用纯粹的XML声明式途径。

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

<?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.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://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>

以上配置的结果是一个fooServicebean,它具有适用于它的所需排序的剖析和事务切面。如果你想要在事务advice在进入之后,事务advice出去之前,执行的剖析advice,你只需简单的更换剖析切面bean的order属性值,使得它比事务advice的顺序值更高。

你使用类似的方式配置其他的切面。

1.5.9 使用带有切面的@Transactional

在一个Spring容器之外,通过AspectJ切面,也可能使用Spring框架的@Transactional的支持。如果这样做,你首先使用@Transactional注解你的类(和可选择的你的类方法),然后你使用定义在spring-aspects.jarorg.springframework.transaction.aspectj.AnnotationTransactionAspect连接(织入)你的应用。还必须使用事务管理器配置你的切面。你当然可以使用Spring框架的IoC容器去管理依赖注入切面。配置事务管理器切面的最简单方式是使用<tx:annotation-driven/>元素和给aspectj指定mode属性,正如在Section 16.5.6 Using @Transactional。因为我们关注在一个Spring容器之外程序的运行,我们将会向你展示如何以编程的方式实现它。

在继续之前,你可能想要分别去读Section 16.5.6 Using @TransactionalChapter 10, Aspect Oriented Programming with Spring

1
2
3
4
5
6

// 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注解覆盖类提供注解(如果存在)的默认事务语义。不管是否可见,任何的方法可能被注解。

使用AnnotationTransactionAspect织入你的应用,你要么使用AspectJ(AspectJ Development Guide)构建你的应用,要么使用加载时织入。使用AspectJ加载时织入的讨论请看Load-time weaving with AspectJ in the Spring Framework

1.6 编程事务管理

Spring框架提供两种编程事务管理工具:

  • 使用TransactionTemplate
  • 直接使用PlatformTransactionManager实现。

对于编程式事务管理Spring团队通常建议使用TransactionTemplate。第二种相似途径是使用JTAUserTransactionAPI,虽然异常处理不是那么笨重。

1.6.1 使用TransactionTemplate

TransactionTemplate 采用与其他Spring模版(例如JdbcTemplate)一样的方式。它使用一个回调的方法,使应用代码不必执行样板获取和释放事务资源,并产生驱动程序的代码,以便于被编写的代码仅关注开发人员想要做的事情。

正如你将在如下的例子中看到的,使用TransactionTemplate绝对将你和Spring的事务基础架构和API结合在一起。

应用代码必须在事务上下文中执行,明确的使用TransactionTemplate,看起来和如下的相似。你作为一个应用开发者,写一个TransactionCallback实现(通常表现为一个匿名内部类),这个实现包含在一个事务上下文中执行的代码。然后将自定义TransactionCallback的实例,传递给TransactionTemplate上公开的execute(...)方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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) {
Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
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类和一个匿名内部类如下:

1
2
3
4
5
6
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
updateOperation1();
updateOperation2();
}
});

在回调中的代码可以通过提供的TransactionStatus对象上调用setRollbackOnly()方法回滚这个事务。

1
2
3
4
5
6
7
8
9
10
11
12

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
updateOperation1();
updateOperation2();
} catch (SomeBusinessExeption ex) {
status.setRollbackOnly();
}
}
});

指定事务设置

你可以以编程方式或配置方式在TransactionTemplate上指定例如传播特性模式,隔离等级,超时等等的事务设置。默认的TransactionTemplate实例有默认事务设置。如下的例子展示对于一个指定TransactionTemplate事务设置的编程式定制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SimpleService implements Service {

private final TransactionTemplate transactionTemplate;

public SimpleService(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
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...
}
}

如下的例子定义了一个使用一些自定义事务设置的TransactionTemplate,使用Spring XML配置。然后可以将sharedTransactionTemplate注入到尽可能多的需要的服务中。

1
2
3
4
5
<bean id="sharedTransactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
<property name="timeout" value="30"/>
</bean>

最后,TransactionTemplate类的实例是线程安全的,因为实力不保持任何会话状态。但是,TransactionTemplate实例会保持配置状态,因此当许多类可能共享一个TransactionTemplate的一个单例时,如果一个类需要使用一个带有不同设置(例如,不同隔离等级)的TransactionTemplate,你就需要去创建一个两个不同的TransactionTemplate实例。

1.6.2 使用PlatformTransactionManager

你也可以直接使用org.springframework.transaction.PlatformTransactionManager管理你的事务。简单的通过一个bean引用传递你正在用的PlatformTransactionManager的实现给你的bean。然后,使用TransactionDefinitionTransactionStatus对象你可以发起,回滚和提交事务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done 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);

1.7 编程式和声明式事务管理器的选择

编程式事务管理仅仅在你如果有一个小数量的事务操作情况下才是一个好想法。例如,如果你有一个web应用需要仅仅主要是update操作的事务,你不可能想去使用Spring或者其他任何技术去设置事务代理。在这种情况下,使用transactionTemplate可能是一个好方法。可能明确的设置事务名称可以仅在使用编程式途径进行事务管理的情况下去做。

从另一方面,如果你的应用有很多事务操作,声明式事务管理通常是合算的。它保持事务管理摆脱业务逻辑,配置不困难。当使用Spring框架,而不是EJB CMT时,声明式事务配置的成本将大大降低。

1.8 事务约束事件

作为Spring 4.2,一个事件的监听可以被约束为事务的一个阶段。常用的例子是当事务已经成功的完成时去处理事件:当当前事务的结果对于监听器实际上很重要时,这允许事件被更灵活地使用。
注册一个常规的监听事件可以通过@EventListener注解。如果你需要和事务绑定它使用@TransactionEventListener。当你这样做时,监听器将默认约束事务提交阶段。
我们举个例子说明这个概念。

假设一个组件发布了一个订单创建的事件,我们想要定义一个监听器,该监听器只应该在事件成功提交时才处理该事件:

1
2
3
4
5
6
7
8
9
@Component
public class MyComponent {

@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}

}

TransactionalEventListener注解暴露一个phase属性,这个属性允许自定义监听器应该约束事务的哪个phase。这个有效的phases是BEFORE_COMMITAFTER_COMMIT(default),AFTER_ROLLBACKAFTER_COMPLETION,它们聚合事务的完整性(是一个提交或者一个回滚)。

如果没有正在运行的事务,由于我们无法遵守所需的语义,监听器根本不会被调用。但是,它可能通过设置注解的fallbackExecution属性为true去覆盖该行为。

1.9 指定应用服务器的集成

Spring的事务抽象通常是应用服务器不可知。此外,Spring的JtaTransactionManager类,可以可选执行对于JTAUserTransactionTransactionManager对象的JNDI查找,对于后者对象,自动检查地址,该地址因应用服务器而变化。访问JTA TransactionManager允许增强的事务语义,特别是支持事务暂停。了解详细请查看JtaTransactionManagerjavadocs。

Spring的JtaTransactionManager在Java EE应用服务器上运行的标准选择,并已知可以在所有常用的服务器上运行。高级功能(事务暂停)在很多服务器上运行很好,包括GlassFish,JBoss和Geronimo,不需要任何特殊配置。但是,对于完整的支持事务暂停和进一步的高级集成,Spring ship对于WebLogic服务器和WebSphere特殊适配。这些适配在如下章节讨论。

对于标准场景,包括Weblogic Server和WebSphere,考虑使用定制<tx:jta-trasaction-manager/>配置元素。当已配置时,这元素自动检查底层服务器和选择适用于该平台的最好的事务管理器。这意味着你将不需要必须显式的配置指定服务器适配类(在如下章节讨论);相反,它们是自动选择的,标准的JtaTransactionManager是默认的后备。

1.9.1 IBM WebSphere

在WebSphere6.1.0.9及以上版本,建议Spring JTA事务管理器使用WebSphereUowTransactionManager。这个特殊的适配器利用了IBM’s UOWManager API,在WebSphere应用服务器6.0.2.19和以后版本,6.1.0.9和以后版本是适用的。使用这个适配器,驱动Spring事务暂停(暂停/恢复 由PROPAGATION_REQUIRES_NEW发起)是由IBM官方支持的。

1.9.2 Oracle WebLogic Server

在Weblogic Server及以后版本中,你通常将使用WebLogicJtaTransactionManager而不是存JtaTransactionManager类。普通JtaTransactionManager的这个特殊的Weblogic特定的子类在Weblogic管理的事务环境中支持Spring的事务全部功能,超出标准的JTA语义:特性包括事务名称,每个事务的隔离等级,以及在所有的情况下正确的恢复事务。

1.10 常见问题解决

1.10.1 对于特定数据源错误的事务管理器的使用

正确的使用PlatformTransactionManager实现基于你事务技术和需求的选择。正确的使用,Spring框架仅提供一个简单直接和轻便的抽象。如果你正在使用全局事务,你必须使用org.springframework.transaction.jta.JtaTransactionManager类(或者一个指定的应用服务器子类)进行你所有的事务操作。否则事务基础设施在资源(例如容器的DataSource实例)上尝试使用本地事务。这样的本地事务没有意义,一个好的应用服务器把它们当作错误。

1.11 集成资源

了解更多关于Spring框架的事务支持信息:

  • Distributed transaction in Spring,with and without XA是一个JavaWorld演示文稿,其中Spring的David Syer引导您在Spring应用程序中通过分布式事务的七种模式,其中三种模式使用XA,另外四种没有。
  • Java 事务设计策略是一本从InfoQ得到的书,InfoQ提供一个Java事务快速介绍。它还包括如何使用Spring Framework和EJB3配置和使用事务的并排示例。