翻译:官方文档
Spring
框架强大的功能之一就是其提供了全面的事务管理能力。Spring
框架为事务管理提供了统一的抽象层,有以下几方面的好处:
APIs
之间建立了一致的编程模型。这些事务管理APIs
包括了Java Transaction API
(JTA
)、JDBC
、Hibernate
、Java Persistence API
(JPA
)。APIs
更加容易使用。Spring
数据访问抽象层更好地集成。下面几节将会详细地描述Spring
事务管理的特性的技术:
Spring
的事务管理模型而不是其他的事务管理APIs
。Spring
事务管理的核心类,并且讲述如何去配置DataSource
以及如何从各种数据源中获得DataSource
。Spring
提供的声明式事务管理功能。Spring
提供的编程式(显示编码)事务管理。(本章还包括了最佳实践的讨论:应用服务器集成和常见问题的解决办法)。
传统上,Java EE
开发者们有两种事务管理可选择:全局事务管理或者本地事务管理。这两个选择都有一定的局限性。在接下来的两节中本文将对全局和本地事务管理进行回顾,然后讨论Spring Framework
提供的事务管理模型如何解决全局和本地事务管理模型的局限性。
全局事务管理允许你使用多个数据源,通常是关系型数据库和消息队列。应用程序服务器通过JTA
来管理全局事务。但是JTA
本身是一个相当笨重的API
,并且一个JTA
的UserTransaction
通常需要从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 Framework
的事务抽象,它可以运行在任何底层事务基础架构上。使用首选的声明式模型,开发人员通常只需要编写很少或根本不编写与事务管理相关的代码,因此不依赖于Spring Framework
事务API
或任何其他事务API
。
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;
}
这主要是一个服务提供者接口(SPI
),尽管你可以从应用程序代码中以编程的方式使用它。因为PlatformTransactionManager
是一个接口,所以可以根据需要轻松地mock
它。它不与查找策略(如JNDI
)绑定。PlatformTransactionManager
的实现类与Spring Framework IoC
容器中的任何其它对象(或bean
)一样被定义。仅这一点就使Spring Framework
事务成为一个有价值的抽象,即使您使用JTA
时也是如此。与直接使用JTA
相比,你可以更容易地测试事务相关的代码。
同样,按照Spring
的哲学,由任何PlatformTransactionManager
接口的方法抛出的TransactionException
是非受检异常(也就是说,它扩展了java.lang.RuntimeException
类)。事务基础设施故障几乎总是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复,应用程序开发人员仍然可以选择捕获和处理TransactionException
。重要的一点是,开发人员不是被迫这样做的。
getTransaction(..)
方法根据TransactionDefinition
参数返回一个TransactionStatus
对象。如果当前调用堆栈中存在匹配的事务,则返回的TransactionStatus
可能表示一个新事务,也可能表示一个现有事务。后一种情况的含义是,与Java EE
事务上下文一样,TransactionStatus
与执行线程相关联。
TransactionDefinition
接口指定:
Spring
提供了EJB CMT
中熟悉的所有事务传播特性选项。要了解Spring
中事务传播的语义,请参见事务的传播特性。这些设置反映了标准的事务概念。如果有必要,请参阅讨论事务隔离级别和其他核心事务概念的参考资料。理解这些概念对于使用Spring
框架或任何事务管理解决方案都是至关重要的。
TransactionDefinition
接口的定义如下:
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
int TIMEOUT_DEFAULT = -1;
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
@Nullable
String getName();
}
TransactionStatus
接口为事务代码提供了一种控制事务执行和查询事务状态的简单方法。这些概念应该很熟悉,因为它们对所有事务api
都是通用的。下面的清单显示了TransactionStatus
接口的定义:
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
@Override
void flush();
boolean isCompleted();
}
无论您在Spring
中选择声明式事务管理还是编程式事务管理,定义正确的PlatformTransactionManager
接口的实现都是绝对必要的。您通常通过依赖注入来定义此实现。
PlatformTransactionManager
接口的实现通常需要了解其工作环境的知识:JDBC
、JTA
、Hibernate
等等。下面的示例展示了如何定义本地PlatformTransactionManager
的实现(在本例中,使用普通JDBC
)。
您可以通过创建类似如下的bean来定义JDBC DataSource
:
<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
定义的引用。它应该类似于下面的例子:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
如果你在Java EE
容器中使用JTA
,那么你将使用通过JNDI
获得的容器数据源与Spring
的JtaTransactionManager
连接。下面的示例显示了JTA
和JNDI
查找版本:
<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" />
beans>
JtaTransactionManager
不需要知道数据源(或任何其他特定资源),因为它使用容器的全局事务管理基础设施。
上边的定义中的数据源bean使用来自
jee
名称空间的标记。有关更多信息,请参见JEE模式。
您还可以使用Hibernate
本地事务,如下面的示例所示。在这种情况下,您需要定义一个Hibernate
的LocalSessionFactoryBean
,应用程序代码可以使用它来获取Hibernate
的Session
实例。
DataSource
bean定义类似于前面显示的本地JDBC
示例,因此在下面的示例中不显示。
如果
DataSource
(由任何非jta
事务管理器使用)通过JNDI
查找并由Java EE
容器管理,那么它应该是非事务性的,因为管理事务的是Spring
框架(而不是Java EE容器)。
本例中的txManager
bean是HibernateTransactionManager
类型。与DataSourceTransactionManager
需要对DataSource
的引用一样,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.xmlvalue>
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
的DataSource
,HibernateTransactionManager
到Hibernate
的 SessionFactory
,等等)。本节描述应用程序代码(直接或间接地,通过使用JDBC
、Hibernate
或JPA
等持久性API
)如何确保正确地创建、重用和清理这些资源。本节还讨论了如何通过相关的PlatformTransactionManager
(可选地)触发事务同步。
建议首选的方法是使用Spring
最高级别的基于模板的持久性集成api
,或者使用具有事务感知的工厂bean
或代理的本机ORM api
来管理本机资源工厂。这些事务感知解决方案在内部处理资源的创建和重用、清理、资源的可选事务同步和异常映射。因此,用户数据访问代码不必处理这些任务,而可以只关注非样板持久性逻辑。通常,您可以使用本机ORM API
,或者通过使用JdbcTemplate
采用JDBC
访问的模板方法。这些解决方案将在本参考文档的后续章节中详细介绍。
诸如DataSourceUtils
(用于JDBC
)、EntityManagerFactoryUtils
(用于JPA
)、SessionFactoryUtils
(用于Hibernate
)等类属于较低级别的。当您希望应用程序代码直接处理本机持久性API
的资源类型时,您可以使用这些类来确保获得适当的Spring
框架管理的实例、事务(可选地)同步以及流程中发生的异常被正确地映射到一致的API
。
例如,在JDBC
的情况下,您可以使用Spring
的org.springframework.jdbc.datasource.DataSourceUtils
类来代替在DataSource
上调用getConnection()
方法的传统JDBC
方法,如下所示:
Connection conn = DataSourceUtils.getConnection(dataSource);
如果现有事务已经有了与之同步(链接)的连接,则返回该实例。否则,方法调用将触发新连接的创建,该连接将(可选地)同步到任何现有事务,并在同一事务中供后续重用。如前所述,任何SQLException
都被包装在Spring
框架CannotGetJdbcConnectionException
中,这是Spring
框架中非受检的DataAccessException
类型的层次结构之一。这种方法提供了比从SQLException
容易获得的信息更多的信息,并确保了跨数据库甚至跨不同持久性技术的可移植性。
这种方法在没有Spring
事务管理的情况下也可以工作(事务同步是可选的),因此无论是否使用Spring
进行事务管理,都可以使用它。
当然,一旦您使用了Spring
的JDBC
支持、JPA
支持或Hibernate
支持,您通常不喜欢使用DataSourceUtils
或其他帮助类,因为与直接使用相关api
相比,您更乐于使用Spring
的抽象。例如,如果您使用Spring的 JdbcTemplate
或jdbc.object
包去简化JDBC
的使用,需要在后台进行正确的连接检索,不需要编写任何特殊的代码。
在最底层存在一个TransactionAwareDataSourceProxy
类。这是目标DataSource
的代理,它包装目标DataSource
以添加Spring
管理事务的感知。在这方面,它类似于由Java EE
服务器提供的事务JNDI DataSource
。
除非必须调用现有代码并传递标准JDBC DataSource
接口实现,否则几乎不应该需要或希望使用这个类。在这种情况下,这段代码可能是可用的,但是参与了spring
管理的事务。您可以使用前面提到的高级抽象来编写新代码。
大多数
Spring
框架用户选择声明式事务管理。此选项对应用程序代码的影响最小,因此最符合非入侵轻量级容器的理想。
Spring
框架的声明式事务管理通过Spring
面向切面编程(AOP
)得以实现。然而,由于Spring
框架发行版附带了事务方面的代码,并且可能以样板形式使用,所以通常不需要理解AOP
概念就可以有效地使用这些代码。
Spring
框架的声明式事务管理类似于EJB CMT
,因为您可以将事务行为(或缺少事务行为)指定到各个方法级别。如果需要,可以在事务上下文中进行setRollbackOnly()
调用。这两种事务管理类型之间的区别是:
JTA
的EJB CMT
不同,Spring
框架的声明式事务管理可以在任何环境中工作。它可以通过调整配置文件来使用JDBC
、JPA
或Hibernate
处理JTA
事务或本地事务。Spring Framework
声明式事务管理应用于任何类,而不仅仅是EJB
等特殊类。Spring
框架提供了声明式回滚规则,这是一个没有EJB
等效项的特性。提供了对回滚规则的编程和声明支持。Spring
框架允许您使用AOP
自定义事务行为。例如,您可以在事务回滚的情况下插入自定义行为。您还可以添加任意的通知以及事务通知。使用EJB CMT
,您不能影响容器的事务管理,除非使用setRollbackOnly()
。Spring
框架不像高端应用服务器那样支持跨远程调用传播事务上下文。如果您需要这个特性,我们建议您使用EJB
。但是,在使用这种特性之前要仔细考虑,因为通常不希望事务跨越远程调用。TransactionProxyFactoryBean在哪?
Spring 2.0
及以上版本中的声明式事务配置与Spring
以前的版本有很大不同。主要区别在于不再需要配置TransactionProxyFactoryBean
。
Spring 2.0
之前的配置样式仍然是100%
有效的配置。可以将新的视为代表您定义的
TransactionProxyFactoryBean
。
回滚规则的概念非常重要。它们允许您指定哪些异常(和可抛出的异常)应该导致自动回滚。您可以在配置中,而不是在Java
代码中,以声明的方式指定它。因此,尽管您仍然可以在TransactionStatus
对象上调用setRollbackOnly()
来回滚当前事务,但通常您可以指定MyApplicationException
必须始终导致回滚的规则。此选项的显著优点是业务对象不依赖于事务基础结构。例如,它们通常不需要导入Spring
事务api
或其他Spring
api
。
尽管EJB
容器默认行为会自动回滚系统异常(通常是运行时异常)上的事务,但EJB CMT
不会自动回滚应用程序异常(即,除了java.rmi.RemoteException
之外的受检异常)上的事务。虽然声明式事务管理的Spring
默认行为遵循EJB
约定(回滚仅在非受检的异常上是自动的),但是定制这种行为通常很有用。
仅仅告诉您使用@Transactional
注释注释类、将@EnableTransactionManagement
添加到配置中并期望您理解它是如何工作的是不够的。为了提供更深入的理解,本节解释Spring Framework
的声明式事务基础设施在事务相关问题发生时的内部工作方式。
关于Spring
框架的声明式事务支持,需要掌握的最重要的概念是,这种支持是通过AOP代理启用的,事务通知是由元数据(当前基于XML
或注释)驱动的。AOP
与事务元数据的结合产生了一个AOP
代理,它使用一个TransactionInterceptor
和一个合适的PlatformTransactionManager
实现来驱动方法调用周围的事务。
AOP章节将介绍
Spring AOP
。
下面的图像展示了在事务代理上调用方法的概念视图:
考虑以下接口及其伴随的实现。这个例子使用Foo
和Bar
类作为占位符,这样您就可以专注于事务的使用,而不必关注特定的域模型。对于本例来说,DefaultFooService
类在每个实现的方法体中抛出UnsupportedOperationException
实例的事实是好的。该行为允许您看到事务被创建,然后回滚以响应UnsupportedOperationException
实例。下面的清单显示了FooService
接口:
// the service interface that we want to make transactional
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();
}
}
假设FooService
接口的前两个方法getFoo(String)
和getFoo(String, String)
必须在具有只读语义的事务的上下文中执行,而其他方法insertFoo(Foo)
和updateFoo(Foo)
必须在具有读写语义的事务的上下文中执行。下面几段将详细解释如何配置:
<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"/>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
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>
如果要连接的
PlatformTransactionManager
的bean
名称为transactionManager
,则可以忽略事务通知()中的
transaction-manager
属性。如果您想要连接的PlatformTransactionManager
bean有任何其他名称,则必须显式地使用transaction-manager
属性,如前面的示例所示。
定义确保txAdvice
bean定义的事务通知在程序中的适当位置执行。首先,定义一个切入点,它匹配在FooService
接口(fooServiceOperation
)中定义的任何操作的执行。然后使用advisor
工具将切入点与txAdvice
关联起来。结果表明,在执行fooServiceOperation
时,将运行txAdvice
定义的通知。
在
元素中定义的表达式是一个AspectJ
切入点表达式。有关Spring
中的切入点表达式的更多细节,请参阅AOP部分。
一个常见的需求是使整个服务层具有事务性。最好的方法是改变切入点表达式以匹配服务层中的任何操作。下面的示例展示了如何这样做:
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
aop:config>
在前面的示例中,假设您的所有服务接口都在
x.y.service
包中定义。有关更多细节,请参见AOP部分。
现在我们已经分析了配置,您可能会问自己,所有这些配置实际上做了什么?
前面显示的配置用于围绕从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
堆栈跟踪已被截断,以确保清晰):
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction
[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]
[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)
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)
上一节概述了如何在应用程序中以声明方式为类(通常是服务层类)指定事务设置的基础知识。本节描述如何以简单的声明式方式控制事务回滚。
要向Spring Framework
的事务基础结构指示要回滚事务的工作,建议的方法是从当前在事务上下文中执行的代码中抛出异常。Spring Framework
的事务基础结构代码在调用堆栈中产生气泡时捕捉任何未处理的异常,并决定是否将事务标记为回滚。
在其默认配置中,Spring Framework
的事务基础结构代码将事务标记为仅在运行时非受检异常的情况下回滚。也就是说,当抛出的异常是RuntimeException
的实例或子类时。(默认情况下,错误实例也会导致回滚)。从事务方法抛出的受检异常不会导致默认配置中的回滚。
您可以准确地配置哪些异常类型将事务标记为回滚,包括选中的异常。下面的XML
片段演示了如何为已检查的特定于应用程序的异常类型配置回滚:
<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 Framework
的事务基础结构,即使在抛出了未处理的InstrumentNotFoundException
异常面前,也要提交相应的事务:
<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 Framework
的事务基础结构中。下面的示例显示如何以编程方式指示所需的回滚:
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// 以编程方式触发回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
如果可能的话,强烈建议您使用声明的方式指定回滚。如果您确实需要,可以使用编程式回滚,但是它的使用与实现干净的基于pojo
的体系结构背道而驰。
考虑这样一个场景:您有许多服务层对象,并且您希望对每个服务层对象应用完全不同的事务配置。可以通过定义不同的
元素,这些元素具有不同的切入点和advice-ref
属性值。
作为比较,首先假设您的所有服务层类都定义在根x.y.service
包中。要使包(或子包)中定义的类实例以及名称以Service
结尾的所有bean
都具有默认的事务配置,可以编写以下代码:
<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>
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<bean id="barService" class="x.y.service.extras.SimpleBarService"/>
<bean id="anotherService" class="org.xyz.SomeService"/>
<bean id="barManager" class="x.y.service.SimpleBarManager"/>
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
tx:attributes>
tx:advice>
beans>
下面的示例展示了如何使用完全不同的事务设置配置两个不同的bean
:
<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>
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<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>
beans>
可配置项本节总结了使用
标签的各种事务配置项。
的默认配置是:
REQUIRED
。DEFAULT
。读写
的。none
。RuntimeException
都会触发回滚,而任何受检的异常都不会。你可以改变这些默认配置。下表总结了嵌套在
和
标签中的
标签的各种属性:
设置:
属性 | 是否必须 | 默认值 | 描述 |
---|---|---|---|
name |
是 | 与事务属性关联的方法名。通配符(*) 字符可用于将相同的事务属性设置与许多方法关联(例如get* 、handle* 、on*Event 等)。 |
|
propagation |
否 | REQUIRED |
事务传播行为。 |
isolation |
否 | DEFAULT |
事务隔离级别。仅适用于REQUIRED 或REQUIRES_NEW 的传播设置。 |
timeout |
否 | -1 | 事务超时(秒)。仅适用于传播REQUIRED 或REQUIRES_NEW 。 |
read-only |
否 | false | 读写事务与只读事务。仅适用于REQUIRED 或REQUIRES_NEW 。 |
rollback-for |
否 | 触发回滚的用逗号分隔的异常实例列表。例如,com.foo.MyBusinessException,ServletException 。 |
|
no-rollback-for |
否 | 不触发回滚的用逗号分隔的异常实例列表。例如,com.foo.MyBusinessException,ServletException 。 |
@Transactional
除了基于xml
的事务配置声明方法外,还可以使用基于注释的方法。在Java
源代码中直接声明事务语义使声明更接近受影响的代码。不存在过度耦合的危险,因为用于事务处理的代码几乎总是以这种方式部署的。
标准
javax.transaction
。事务性注释也支持作为Spring
自身注释的替代。有关详细信息,请参阅JTA 1.2文档
。
使用@Transactional
注解所提供的易用性最好通过一个示例进行说明,该示例将在下面的文本中进行解释。考虑以下类定义:
// 需要进行事务管理的Service类
@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
上下文中定义为bean
时,您可以通过@Configuration
类中的@EnableTransactionManagement
注解使bean
实例具有事务性。有关详细信息,请参见javadoc。
在XML配置中,
标记提供了类似的便利:
<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"/>
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
beans>
如果要连接的
PlatformTransactionManager
的bean
名称为transactionManager
,则可以忽略标记中的
transaction-manager
属性。如果要依赖注入的PlatformTransactionManager
bean
有任何其他名称,则必须使用transaction-manager
属性,如前面的示例所示。
您可以将@Transactional
注释应用于接口定义、接口上的方法、类定义或类上的公共方法。然而,仅仅存在@Transactional
注释不足以激活事务行为。@Transactional
注释只是一些运行时基础设施可以使用的元数据,这些运行时基础设施能够感知@Transactional
,可以使用元数据配置具有事务行为的适当bean
。在前面的示例中,
元素在事务行为上进行切换。
Spring
团队建议使用@Transactional
注释只注释具体类(和具体类的方法),而不是注释接口。您当然可以将@Transactional
注释放置在接口(或接口方法)上,但是只有在使用基于接口的代理时,它才能正常工作。Java
注释没有从接口继承这一事实意味着,如果您使用基于类的代理(proxy-target-class="true"
)或基于编织的方面(mode="aspectj"
),那么代理和编织基础设施无法识别事务设置,并且对象也没有封装在事务代理中。
在代理模式(默认模式)中,只有通过代理传入的外部方法调用被拦截。这意味着自调用(实际上,目标对象中的方法调用目标对象的另一个方法)不会在运行时导致实际的事务,即使调用的方法被标记为
@Transactional
。此外,代理必须完全初始化才能提供预期的行为,因此您不应该在初始化代码(即@PostConstruct
)中依赖这个特性。
如果您希望自调用也被事务包装,那么可以考虑使用AspectJ
模式(请参阅下表中的mode
属性)。在这种情况下,首先没有代理。相反,目标类被编织(也就是说,它的字节码被修改)以将@Transactional
转换为任何类型方法的运行时行为。
基于注解的事务管理配置项
xml中的属性 | 注解中的属性 | Default | Description |
---|---|---|---|
transaction-manager |
N/A (see TransactionManagementConfigurer javadoc) |
transactionManager |
要使用的事务管理器的名称。仅当事务管理器的名称不是transactionManager 时才需要,如前面的示例所示。 |
mode |
mode |
proxy |
默认模式(proxy )使用Spring的AOP框架处理要代理的带注释的bean(按照前面讨论的代理语义,仅应用于通过代理传入的方法调用)。替代模式(aspectj )将受影响的类与Spring的aspectj事务方面编织在一起,修改目标类字节码以应用于任何类型的方法调用。AspectJ编织需要spring方面。类路径中的jar以及启用加载时编织(或编译时编织)。(有关如何设置加载时编织的详细信息,请参见Spring配置)。 |
proxy-target-class |
proxyTargetClass |
false |
仅适用于代理模式。控制为使用@Transactional 注释注释的类创建哪种类型的事务代理。如果proxy-target-class 属性设置为true ,则创建基于类的代理。如果proxy-target-class 为false ,或者属性被省略,那么就会创建基于JDK接口的标准代理。(有关不同代理类型的详细研究,请参见代理机制)。 |
order |
order |
Ordered.LOWEST_PRECEDENCE |
定义应用于用@Transactional 注释的bean 的事务通知的顺序。(有关AOP通知排序相关规则的更多信息,请参见通知排序)。没有指定的顺序意味着AOP子系统决定通知的顺序。 |
处理
@Transactional
注释的默认通知模式是proxy
,它只允许通过代理拦截调用。同一类中的本地调用不能以这种方式被拦截。对于更高级的侦听模式,可以考虑结合编译时或加载时编织切换到aspectj
模式。
proxy-target-class
属性控制为使用@Transactional
注释的类创建哪种类型的事务代理。如果proxy-target-class
设置为true
,则创建基于类的代理。如果proxy-target-class
为false
,或者属性被省略,那么将创建基于JDK接口的标准代理。(有关不同代理类型的讨论,请参阅aop-proxy。)
@EnableTransactionManagement
和标签仅在定义它们的应用程序上下文中查找
@Transactional
。这意味着,如果将注释驱动的配置放在DispatcherServlet
的WebApplicationContext
中,则它只检查controllers
中的@Transactional
bean,而不检查services
中的@Transactional
bean。有关更多信息,请参见MVC。
在计算哪个配置对方法上的事务有效时,越接近方法的位置上的配置优先级越高。在下面的示例中,DefaultFooService
类在类级别上使用只读事务的设置进行注释,但是相同类中的updateFoo(Foo)
方法上的@Transactional
注释优先于在类级别上定义的事务设置。
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// do something
}
// 对于这个方法来说,在这里配置的事务配置优先级更高
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}
@Transactional
的配置项@Transactional
注释是指定接口、类或方法必须具有事务语义的元数据(例如,“在调用该方法时启动一个全新的只读事务,挂起任何现有事务”)。默认的@Transactional
设置如下:
事务的传播特性默认是PROPAGATION_REQUIRED
。
事务的隔离级别默认是ISOLATION_DEFAULT
。
事务默认是可读写
的。
事务超时默认为基础事务系统的默认超时,如果不支持超时,则为none
。
任何RuntimeException
都会触发回滚,而任何受检的异常都不会触发回滚。
你可以改变这些默认配置。下表总结了@Transactional
注释的各种属性:
@Transactional 配置项
属性 | 类型 | Description |
---|---|---|
value | String |
可选限定符,指定要使用的事务管理器。 |
propagation | enum : Propagation |
Optional propagation setting. |
isolation |
enum : Isolation |
可选的隔离级别配置。仅适用于REQUIRED 或REQUIRES_NEW 的传播值。 |
timeout |
int (单位是:秒) |
可选的事务超时。仅适用于REQUIRED 或REQUIRES_NEW 的传播值。 |
readOnly |
boolean |
读写事务与只读事务。仅适用于REQUIRED 或REQUIRES_NEW 的值。 |
rollbackFor |
异常类的Class 对象数组 |
可选的导致回滚的异常类Class 对象数组。 |
rollbackForClassName |
异常类的类名数组 | 可选的导致回滚的异常类的类名数组。 |
noRollbackFor |
异常类的Class 对象数组 |
可选的不导致回滚的异常类Class 对象数组。 |
noRollbackForClassName |
异常类的类名数组 | 可选的不导致回滚的异常类的类名数组。 |
目前,您无法显式控制事务的名称,其中“名称”指的是出现在事务监视器(如WebLogic 的事务监视器)和日志输出中的事务名称。对于声明式事务,事务名总是全限定类名
+.
+被事务通知的方法名
。例如,如果BusinessService
类的handlePayment(..)
方法启动了一个事务,那么事务的名称应该是:com.example.BusinessService.handlePayment
。
@Transactional
配置多个事务管理器大多数Spring
应用程序只需要一个事务管理器,但是在某些情况下,您可能希望在一个应用程序中有多个独立的事务管理器。您可以使用@Transactional
注释的value
属性来选择性地指定要使用的PlatformTransactionManager
的实现类。这可以是bean
名,也可以是事务管理器bean
的限定符值。例如,使用限定符符号,您可以在应用程序上下文中将以下Java
代码与以下事务管理器bean
声明组合起来:
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·限定符区分。默认的
目标bean
名称transactionManager
,如果没有找到特定的符合条件的PlatformTransactionManager
bean,则仍然使用它。
如果你发现你经常做重复性的工作,例如在许多不同的方法上重复使用@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
中事务传播特性的一些语义。
在spring
管理的事务中,请注意物理事务和逻辑事务之间的差异,以及传播特性的设置如何应用于这种差异。
PROPAGATION_REQUIRED
PROPAGATION_REQUIRED
强制开启一个物理事务,如果当前作用域内不存在事务则在本地执行该事务,或者参与到外部更大作用域范围的事务中执行。在同一线程内的公共调用堆栈安排中,这是一个很好的默认设置(例如,将服务facade委托给多个存储库方法,其中所有底层资源都必须参与服务级事务)。
默认情况下,参与事务连接外部作用域的特征,静默地忽略本地隔离级别、超时值或只读标志(如果有的话)。如果您希望在使用不同的隔离级别参与现有事务时拒绝隔离级别声明,请考虑将事务管理器上的
validateExistingTransactions
标志切换为true
。这种非宽松模式还拒绝只读不匹配(即试图参与只读外部作用域的内部读写事务)。
当事务的传播特性设置为PROPAGATION_REQUIRED
时,将为这个设置应用的每个方法创建一个逻辑事务作用域。每个这样的逻辑事务作用域可以单独确定rollback-only
状态,外部事务作用域在逻辑上独立于内部事务作用域。在标准的PROPAGATION_REQUIRED
行为的情况下,所有这些作用域都映射到相同的物理事务。因此,在内部事务范围中设置的rollback-only
标记确实会影响外部事务实际提交的机会。
但是,在内部事务范围设置rollback-only
标记的情况下,外部事务没有决定回滚本身,因此回滚(由内部事务范围静默触发)是意外的。这时会抛出一个对应的UnexpectedRollbackException
。这是预期的行为,这样事务的调用者就永远不会被误导,以为提交是在实际上没有执行的情况下执行的。因此,如果一个内部事务(外部调用方不知道该事务)无声地将一个事务标记为rollback-only
,那么外部调用方仍然调用commit
。外部调用者需要接收一个UnexpectedRollbackException
,以清楚地表明执行了回滚。
PROPAGATION_REQUIRES_NEW
PROPAGATION_REQUIRES_NEW
,和PROPAGATION_REQUIRED
相反,始终为每个受影响的事务作用域使用独立的物理事务,从不参与外部作用域所在的事务。在这种安排中,底层资源事务是不同的,因此可以独立地提交或回滚,外部事务不受内部事务回滚状态的影响,内部事务在完成后立即释放锁。这样一个独立的内部事务还可以声明它自己的隔离级别、超时和只读设置,而不继承外部事务的特征。
PROPAGATION_NESTED
PROPAGATION_NESTED
使用具有多个保存点的单个物理事务,可以回滚到这些保存点。这种部分回滚允许内部事务作用域触发其作用域的回滚,尽管回滚了一些操作,但外部事务仍然能够继续它的物理事务。该设置通常映射到JDBC
保存点,因此它只对JDBC
资源事务有效。参见Spring
的DataSourceTransactionManager。
假设您希望同时执行事务操作和一些基本的分析通知。如何在
的上下文中实现这一点呢?
当您调用updateFoo(Foo)
方法时,您希望看到以下操作:
本章不涉及详细解释
AOP
(除非它应用于事务)。有关AOP
配置和一般AOP
的详细内容,请参见AOP。
下面的代码展示了前面讨论的简单分析切面:
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;
// 允许控制通知的执行顺序
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
// profile这个方法被定义为一个环绕通知
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;
}
}
通知的顺序是通过Ordered
接口控制的。有关通知的顺序详细信息,请参见 Advice ordering 。
下面的配置将创建一个fooService
bean,该bean
具有按所需顺序应用于其上的分析和事务切面:
<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"/>
<bean id="profiler" class="x.y.SimpleProfiler">
<property name="order" value="1"/>
bean>
<tx:annotation-driven transaction-manager="txManager" order="200"/>
<aop:config>
<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
声明方法:
<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"/>
<bean id="profiler" class="x.y.SimpleProfiler">
<property name="order" value="1"/>
bean>
<aop:config>
<aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
<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>
beans>
前面配置的结果是一个fooService
bean,它具有按此顺序应用于它的分析和事务方面。如果您希望分析建议在传入的事务建议之后执行,在传出的事务建议之前执行,那么您可以交换分析方面bean的order属性的值,使其高于事务建议的order值。
您可以以类似的方式配置其他方面。
AspectJ
使用@Transactional
您还可以通过AspectJ
切面在Spring
容器之外使用Spring
框架的@Transactional
支持。为此,首先用@Transactional
注释注释类(以及可选的类方法),然后使用在spring-aspects.jar
中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect
链接(编织)应用程序。您还必须配置事务管理器的切面。您可以使用Spring Framework
的IoC
容器来处理依赖注入方面。配置事务管理方面最简单的方法是使用
元素,并像使用@Transactional中描述的那样为aspectj
指定模式属性。因为我们在这里关注的是在Spring
容器之外运行的应用程序,所以我们将向您展示如何通过编程来实现这一点。
在继续之前,您可能希望分别使用@Transactional和AOP进行读取。
下面的例子展示了如何创建一个事务管理器并配置AnnotationTransactionAspect
来使用它:
// 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开发指南),或者使用加载时编织。有关使用AspectJ
进行加载时编织的讨论,请参见Spring框架中的AspectJ加载时编织。
Spring
框架提供了两种编程式事务管理方法,通过使用:
TransactionTemplate
。PlatformTransactionManager
接口的直接实现类。Spring
团队通常推荐使用TransactionTemplate
进行编程式事务管理。第二种方法类似于使用JTA
UserTransaction
API
,尽管异常处理不那么麻烦。
TransactionTemplate
TransactionTemplate
采用与其他Spring
模板(如JdbcTemplate
)相同的方法。它使用回调方法(将应用程序代码从获取和释放事务资源的样板中解放出来),并产生意图驱动的代码,因为您的代码只关注您想要做的事情。
如下面的示例所示,使用
TransactionTemplate
绝对会将您与Spring
的事务基础结构和api
结合在一起。编程式事务管理是否适合您的开发需求是您必须自己做出的决定。
必须在事务上下文中执行且显式使用TransactionTemplate
的应用程序代码类似于下一个示例。作为应用程序开发人员,您可以编写一个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
实例具有默认的事务设置。下面的示例显示了特定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
时,声明式事务管理的配置成本会大大降低。
从Spring 4.2
开始,事件的侦听器可以绑定到事务的某个阶段。典型的例子是在事务成功完成时处理事件。当当前事务的结果对侦听器很重要时,这样做可以更灵活地使用事件。
您可以使用@EventListener
注解注册一个常规事件侦听器。如果需要将其绑定到事务,请使用@TransactionalEventListener
。当您这样做时,默认情况下侦听器被绑定到事务的提交阶段。
下一个例子展示了这个概念。假设一个组件发布了一个订单创建的事件,并且我们希望定义一个侦听器,该侦听器只应该在发布该事件的事务成功提交之后处理该事件。下面的示例设置这样的事件侦听器:
@Component
public class MyComponent {
@TransactionalEventListener
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
...
}
}
@TransactionalEventListener
注解公开了一个phase
属性,该属性允许您自定义应该将侦听器绑定到的事务的阶段。有效的阶段包括BEFORE_COMMIT
、AFTER_COMMIT
(默认)、AFTER_ROLLBACK
和AFTER_COMPLETION
,它们聚合事务完成(无论是提交还是回滚)。
如果没有事务正在运行,则根本不会调用侦听器,因为我们无法遵守所需的语义。但是,您可以通过将注释的fallbackExecution
属性设置为true
来覆盖该行为。
Spring
的事务抽象通常与应用服务器无关。此外,Spring
的 JtaTransactionManager
类(它可以选择性地为JTA
UserTransaction
和TransactionManager
对象执行JNDI
查找)自动检测后一个对象的位置,后者的位置因应用服务器的不同而不同。访问JTA
TransactionManager
允许增强事务语义,特别是支持事务挂起。有关详细信息,请参见JtaTransactionManager javadoc。
Spring
的 JtaTransactionManager
是在Java EE
应用服务器上运行的标准选择,众所周知,它可以在所有公共服务器上运行。高级功能(如事务挂起)也可以在许多服务器上工作(包括GlassFish
、JBoss
和Geronimo
),而不需要任何特殊配置。但是,对于完全支持的事务挂起和进一步的高级集成,Spring
包含针对WebLogic Server
和WebSphere
的特殊适配器。下面几节将讨论这些适配器。
对于标准场景,包括WebLogic Server
和WebSphere
,可以考虑使用方便的
配置元素。配置后,此元素自动检测底层服务器并选择平台可用的最佳事务管理器。这意味着您不需要显式地配置特定于服务器的适配器类(如下面的部分所述)。相反,它们是自动选择的,标准的JtaTransactionManager
是默认的回退方法。
在WebSphere 6.1.0.9
及以上版本中,推荐使用的Spring
JTA
事务管理器是WebSphereUowTransactionManager
。这个特殊适配器使用IBM
的 UOWManager
API
,该API
在WebSphere Application Server 6.1.0.9
及更高版本中可用。使用此适配器,IBM
正式支持Spring
驱动的事务挂起(由PROPAGATION_REQUIRES_NEW
发起的挂起和恢复)。
在WebLogic Server 9.0
或更高版本上,您通常使用WebLogicJtaTransactionManager
而不是stock JtaTransactionManager
类。这个特殊的特定于weblogic
的普通JtaTransactionManager
子类支持weblogic
管理的事务环境中Spring
事务定义的全部功能,超出了标准的JTA
语义。特性包括事务名称、每个事务隔离级别以及在所有情况下正确恢复事务。
本节描述一些常见问题的解决方案。
DataSource
使用了错误的事务管理根据您对事务技术和需求的选择,使用正确的PlatformTransactionManager
实现。如果使用得当,Spring
框架仅仅提供了一个简单而可移植的抽象。如果使用全局事务,则必须使用org.springframework.transaction.jta.JtaTransactionManager
类(或其特定于应用程序服务器的子类)。否则,事务基础结构将尝试在容器数据源实例等资源上执行本地事务。这样的本地事务没有意义,好的应用服务器会将它们视为错误。
有关Spring
框架事务支持的更多信息,请参见:
JavaWorld
表示,其中Spring
的David Syer
指导您了解Spring
应用程序中分布式事务的7
种模式,其中3
种带有XA
, 4
种没有。InfoQ
提供了一本书,对Java
事务进行了快速的介绍。它还包括如何使用Spring
框架和EJB3
配置和使用事务的并列示例。