1.事务管理
全面的事务支持是使用Spring Framework的最有说服力的理由之一。 Spring Framework为事务管理提供了一致的抽象,具有以下优势:
- 跨不同事务API的一致编程模型,例如Java Transaction API(JTA),JDBC,Hibernate和Java Persistence API(JPA)。
- 支持声明式事务管理。
- 比复杂事务API(如JTA)更简单的编程事务管理API。
- 与Spring的数据访问抽象集成。
以下部分描述了Spring Framework的事务功能和技术:
- Spring Framework的事务支持模型的优点描述了为什么要使用Spring Framework的事务抽象而不是EJB容器管理事务(CMT)或选择通过专有API(如Hibernate)来驱动本地事务。
- 理解Spring Framework事务抽象概述了核心类,并描述了如何从各种源配置和获取DataSource实例。
- 将资源与事务同步描述了应用程序代码如何确保正确创建,重用和清理资源。
- 声明式事务管理描述了对声明式事务管理的支持。
- 程序化事务管理包括对程序化(即明确编码)事务管理的支持。
- 事务绑定事件描述了如何在事务中使用应用程序事件。
本章还包括对最佳实践,应用程序服务器集成以及常见问题解决方案的讨论。
传统上,Java EE开发人员有两种事务管理选择:全局或本地事务,这两种事务都有很大的局限性。在接下来的两节中将对全局和本地事务管理进行审查,然后讨论Spring Framework的事务管理支持如何解决全局和本地事务模型的局限性。
全局事务允许你使用多个事务资源,通常是关系数据库和消息队列。应用程序服务器通过JTA管理全局 事务,这是一个繁琐的API(部分原因是它的异常模型)。此外,JTA UserTransaction通常需要从JNDI获取,这意味着你还需要使用JNDI才能使用JTA。全局事务的使用限制了应用程序代码的任何潜在重用,因为JTA通常仅在应用程序服务器环境中可用。
以前,使用全局事务的首选方法是通过EJB CMT(容器管理事务)。 CMT是一种声明式事务管理(与程序化事务管理不同)。 EJB CMT消除了与事务相关的JNDI查找的需要,尽管EJB本身的使用需要使用JNDI。它消除了编写Java代码以控制事务的大部分但不是全部的需要。重要的缺点是CMT与JTA和应用服务器环境相关联。此外,仅当选择在EJB中实现业务逻辑(或至少在事务EJB外观之后)时,它才可用。一般来说,EJB的负面影响是如此之大,以至于这不是一个有吸引力的主张,特别是面对声明式事务管理的令人信服的替代方案。
本地事务是特定于资源的,例如与JDBC连接关联的事务。本地事务可能更容易使用,但具有明显的缺点:它们无法跨多个事务资源工作。例如,使用JDBC连接管理事务的代码无法在全局JTA事务中运行。由于应用程序服务器不参与事务管理,因此无法确保跨多个资源的正确性。 (值得注意的是,大多数应用程序使用单个事务资源。)另一个缺点是本地事务对编程模型是侵入性的。
Spring解决了全局和本地事务的缺点。它允许应用程序开发人员在任何环境中使用一致的编程模型你只需编写一次代码,就可以从不同环境中的不同事务管理策略中受益。 Spring Framework提供了声明式和编程式事务管理。大多数用户更喜欢声明式事务管理,我们建议在大多数情况下使用。
通过编程式事务管理,开发人员可以使用Spring Framework事务抽象,它可以在任何底层事务基础结构上运行。使用首选的声明性模型,开发人员通常很少编写或不编写与事务管理相关的代码,因此不依赖于Spring Framework事务API或任何其他事务API。
你是否需要应用程序服务器进行事务管理?Spring Framework的事务管理支持改变了关于企业Java应用程序何时需要应用程序服务器的传统规则。特别是,你不需要纯粹用于通过EJB进行声明式事务的应用程序服务器。实际上,即使你的应用程序服务器具有强大的JTA功能,你也可以决定Spring Framework的声明式事务提供比EJB CMT更强大的功能和更高效的编程模型。通常,只有当你的应用程序需要处理跨多个资源的事务时,才需要应用程序服务器的JTA功能,这对许多应用程序来说并不是必需的。许多高端应用程序使用单个高度可伸缩的数据库(例如Oracle RAC)。独立的事务管理器(例如Atomikos Transactions和JOTM)是其他选项。当然,你可能需要其他应用程序服务器功能,例如Java消息服务(JMS)和Java EE连接器体系结构(JCA)。Spring Framework使你可以选择何时将应用程序扩展到完全加载的应用程序服务器。使用EJB CMT或JTA的唯一替代方法是使用本地事务(例如JDBC连接上的代码 )编写代码,并且如果你需要在全局容器管理的事务中运行代码,则会面临大量的返工。使用Spring Framework,只需要更改配置文件中的一些bean定义(而不是代码)。
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是一个接口,所以可以根据需要轻松地模拟或存根。它与查找策略无关,例如JNDI。 PlatformTransactionManager实现的定义与Spring Framework IoC容器中的任何其他对象(或bean)相同。仅使用此优势使Spring Framework事务成为一种有价值的抽象,即使你使用JTA也是如此。与直接使用JTA相比,你可以更轻松地测试事务代码。
同样,为了与Spring的理念保持一致,可以取消选中任何PlatformTransactionManager接口的方法抛出的TransactionException(即,它扩展了java.lang.RuntimeException类)。交易基础设施故障几乎总是致命的。在极少数情况下,应用程序代码实际上可以从事务失败中恢复,应用程序开发人员仍然可以选择捕获并处理TransactionException。重点是开发人员不会被迫这样做。
getTransaction(..)方法返回TransactionStatus对象,具体取决于TransactionDefinition参数。如果当前调用堆栈中存在匹配的事务,则返回的TransactionStatus可能表示新事务或可以表示现有事务。后一种情况的含义是,与Java EE事务上下文一样,TransactionStatus与执行线程相关联。
TransactionDefinition接口指定:
- Propagation:通常,在事务范围内执行的所有代码都在该事务中运行。但是,如果在事务上下文已存在时执行事务方法,则可以指定行为。例如,代码可以继续在现有事务中运行(常见情况),或者可 以暂停现有事务并创建新事务。 Spring提供了EJB CMT中熟悉的所有事务传播选项。要阅读有关Spring中事务传播的语义,请参阅事务传播。
- Isolation:此事务与其他事务的工作隔离的程度。例如,此事务是否可以看到来自其他事务的未提交的写入?
- Timeout:此事务在超时之前运行多长时间并由底层事务基础结构自动回滚。
- Read-only status:你可以在代码读取时使用只读事务,但不能修改数据。在某些情况下,只读事务可能是一种有用的优化,例如当你使用Hibernate时。
这些设置反映了标准的事务概念。如有必要,请参阅讨论事务隔离级别和其他核心事务概念的资源。理解这些概念对于使用Spring Framework或任何事务管理解决方案至关重要。
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 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获得的容器DataSource以及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不需要知道DataSource(或任何其他特定资源),因为它使用容器的全局事务管理基础结构。
dataSource bean的前面定义使用jee名称空间中的<jndi-lookup />标记。有关更多信息,请参阅JEE架构。
你还可以轻松使用Hibernate本地事务,如以下示例所示。在这种情况下,你需要定义一个Hibernate LocalSessionFactoryBean,你的应用程序代码可以使用它来获取Hibernate Session实例。
DataSource bean定义类似于前面显示的本地JDBC示例,因此未在以下示例中显示。
如果DataSource(由任何非JTA事务管理器使用)通过JNDI查找并由Java EE容器管理,则它应该是非事务性的,因为Spring Framework(而不是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.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等等)。本节描述应用程序代码(直接或间接使用诸如JDBC,Hibernate或JPA之类的持久性API)如何确保正确创建,重用和清理这些资源。本节还讨论了如何(可选)通过相关的PlatformTransactionManager触发事务同步。
首选方法是使用Spring最高级别的基于模板的持久性集成API,或者将本机ORM API与事务感知工厂bean或代理一起使用,以管理本机资源工厂。这些事务感知解决方案在内部处理资源创建和重用,清理,资源的可选事务同步以及异常映射。因此,用户数据访问代码不必解决这些任务,而是可以完全专注于非样板持久性逻辑。通常,你使用本机ORM API或使用模板方法通过使用JdbcTemplate进行JDBC访问。这些解决方案将在本参考文档的后续章节中详细介绍。
1.3.2。低级同步方法 诸如DataSourceUtils(用于JDBC),EntityManagerFactoryUtils(用于JPA),SessionFactoryUtils(用于Hibernate)等的类存在于较低级别。当你希望应用程序代码直接处理本机持久性API的资源类型时,你可以使用这些类来确保获得正确的Spring Framework托管实例, (可选)同步事务,并且在此过程中发生的异常是正确映射到一致的API。
例如,在JDBC的情况下,你可以改为使用Spring的org.springframework.jdbc.datasource.DataSourceUtils类,而不是传统的JDBC方法来调用DataSource上的getConnection()方法,如下所示:
Connection conn = DataSourceUtils.getConnection(dataSource);
如果现有事务已经与其同步(链接)了连接,则返回该实例。否则,方法调用将触发新连接的创建,该连接(可选)与任何现有事务同步,并可在随后的同一事务中重用。如前所述,任何SQLException都包含在Spring Framework CannotGetJdbcConnectionException中,Spring Framework是未经检查的DataAccessException类型的层次结构之一。这种方法为你提供了比从SQLException轻松获得的更多信息,并确保跨数据库甚至跨不同持久性技术的可移植性。
这种方法在没有Spring事务管理(事务同步是可选的)的情况下也可以工作,因此无论你是否使用Spring进行事务管理,都可以使用它。
当然,一旦你使用了Spring的JDBC支持,JPA支持或Hibernate支持,你通常不希望使用DataSourceUtils或其他帮助程序类,因为你通过Spring抽象工作比直接使用相关API更快乐。例如,如果你使用Spring JdbcTemplate或jdbc.object包来简化JDBC的使用,则在幕后进行正确的连接检索,你无需编写任何特殊代码。
在最低级别存在TransactionAwareDataSourceProxy类。这是目标DataSource的代理,它包装目标DataSource以添加对Spring管理的事务的感知。在这方面,它类似于Java EE服务器提供的事务性JNDI数据源。
你几乎从不需要或不想使用此类,除非必须调用现有代码并传递标准JDBC DataSource接口实现。在这种情况下,此代码可能可用但参与Spring管理的事务。你可以使用前面提到的更高级别的抽象来编写新代码。
大多数Spring Framework用户选择声明式事务管理。此选项对应用程序代码的影响最小,因此与非侵入式轻量级容器的理想最为一致。 使用Spring面向方面编程(AOP),Spring Framework的声明式事务管理成为可能。但是,由于事务方面代码随Spring Framework发行版一起提供并且可能以样板方式使用,因此通常不必理解AOP概念以有效使用此代码。
Spring Framework的声明式事务管理类似于EJB CMT,因为你可以将事务行为(或缺少它)指定到单个方法级别。如有必要,你可以在事务上下文中进行setRollbackOnly()调用。两种类型的事务管理之间的区别是:
与绑定到JTA的EJB CMT不同,Spring Framework的声明式事务管理适用于任何环境。它可以通过调整配置文件使用JDBC,JPA或Hibernate来处理JTA事务或本地事务。
你可以将Spring Framework声明式事务管理应用于任何类,而不仅仅是EJB等特殊类。
Spring Framework提供了声明性回滚规则,这是一个没有EJB等价的功能。提供了对回滚规则的编程和声明性支持。
Spring Framework允许你使用AOP自定义事务行为。例如,你可以在事务回滚的情况下插入自定义行为。你还可以添加任意建议以及事务建议。使用EJB CMT,除了使用setRollbackOnly()之外 ,你无法影响容器的事务管理。
Spring框架不支持跨远程调用传播事务上下文,就像高端应用程序服务器那样。如果你需要此功能,我们建议你使用EJB。但是,在使用此类功能之前请仔细考虑,因为通常情况下,人们不希望事务跨越远程调用。
回滚规则的概念很重要。它们允许你指定哪些异常(和throwable)应该导致自动回滚。你可以在配置中以声明方式指定,而不是在Java代码中。因此,尽管你仍然可以在TransactionStatus对象上调用setRollbackOnly()来回滚当前事务,但大多数情况下你可以指定MyApplicationException必须始终导致回滚的规则。此选项的显着优势是业务对象不依赖于事务基础结构。例如,它们通常不需要导入Spring事务API或其他Spring API。
虽然EJB容器默认行为会自动回滚系统异常(通常是运行时异常)上的事务,但EJB CMT不会在应用程序异常(即java.rmi.RemoteException以外的已检查异常)上自动回滚事务。虽然声明式事务管理的Spring默认行为遵循EJB约定(回滚仅在未经检查的异常上自动执行),但定制此行为通常很有用。
仅仅通过@Transactional注解告诉你注解你的类是不够的,将@EnableTransactionManagement添加到你的配置中,并期望你了解它是如何工作的。为了提供更深入的理解,本节解释了在发生与事务相关的问题时Spring Framework的声明式事务基础结构的内部工作原理。
关于Spring Framework的声明式事务支持,最重要的概念是通过AOP代理启用此支持,并且事务性建议由元数据(当前基于XML或基于注解)驱动。 AOP与事务元数据的组合产生一个AOP代理,该代理使用TransactionInterceptor和适当的PlatformTransactionManager实现来驱动围绕方法调用的事务。
以下图像显示了在事务代理上调用方法的概念视图:

transaction proxy
请考虑以下接口及其附带实现。 此示例使用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),必须在具有读写语义的事务的上下文中执行。 以下配置将在接下来的几段中详细说明:
<!-- 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 bean,transactional。 要应用的事务语义封装在<tx:advice />定义中。 <tx:advice />定义读作“所有方法,从get开始,将在只读事务的上下文中执行,所有其他方法将使用默认事务语义执行”。 <tx:advice />标记的transaction-manager属性设置为将驱动事务的PlatformTransactionManager bean的名称(在本例中为txManager bean)。
如果要连接的PlatformTransactionManager的bean名称具有名称transactionManager,则可以省略事务建议(<tx:advice />)中的transaction-manager属性。 如果要连接的PlatformTransactionManager bean具有任何其他名称,则必须显式使用transaction-manager属性,如上例所示。
<aop:config />定义确保txAdvice bean定义的事务性建议在程序中的适当位置执行。 首先,定义一个切入点,该切入点与FooService接口(fooServiceOperation)中定义的任何操作的执行相匹配。 然后使用顾问程序将切入点与txAdvice相关联。 结果表明,在执行fooServiceOperation时,将运行txAdvice定义的建议。
<aop:pointcut />元素中定义的表达式是AspectJ切入点表达式。 有关Spring中切入点表达式的更多详细信息,请参阅AOP部分。
常见的要求是使整个服务层具有事务性。 执行此操作的最佳方法是更改切入点表达式以匹配服务层中的任何操作。 以下示例显示了如何执行此操作:
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
现在我们已经分析了配置,你可能会问自己,“所有这些配置实际上做了什么?”
前面显示的配置用于围绕从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());
}
}
运行前面的程序的输出应类似于以下内容(为清楚起见,LogFJ输出和DefaultFooService类的insertFoo(..)方法抛出的UnsupportedOperationException中的堆栈跟踪已被截断):
<!-- 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 Framework的事务基础结构指示事务的工作将被回滚的推荐方法是从当前在事务上下文中执行的代码中抛出异常。 Spring Framework的事务基础结构代码捕获任何未处理的异常,因为它冒泡调用堆栈并确定是否将事务标记为回滚。
在其默认配置中,Spring Framework的事务基础结构代码仅在运行时未经检查的异常情况下标记用于回滚的事务。也就是说,抛出的异常是RuntimeException的实例或子类。 (默认情况下,错误实例也会导致回滚)。从事务方法抛出的已检查异常不会导致在默认配置中回滚。
你可以准确配置哪些Exception类型标记用于回滚的事务,包括已检查的异常。以下XML代码段演示了如何为已检查的特定于应用程序的Exception类型配置回滚:
<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 Framework的事务基础结构捕获异常并且它查询配置的回滚规则以确定是否将事务标记为回滚时,最强匹配规则获胜。 因此,在以下配置的情况下,除了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) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
如果可能的话,强烈建议你使用声明性方法进行回滚。 如果你绝对需要程序化回滚,则可以使用程序化回滚,但它的使用方式可以实现基于POJO的简洁体系结构。
考虑具有多个服务层对象的情况,并且你希望对每个对象应用完全不同的事务配置。 你可以通过使用不同的切入点和advice-ref属性值定义不同的<aop:advisor />元素来实现。
作为比较,首先假设你的所有服务层类都在根x.y.service包中定义。 要使所有作为在该包(或子包中)中定义的类的实例的bean以及以Service结尾的名称具有默认的事务配置,你可以编写以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
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 />设置为:
- 传播设置是必需的。
- 隔离级别为DEFAULT。
- 该事务是读写的。
- 事务超时默认为基础事务系统的默认超时,如果不支持超时,则为none。
- 任何RuntimeException都会触发回滚,而任何已检查的Exception都不会。
你可以更改这些默认设置。 下表总结了嵌套在<tx:advice/>和<tx:attributes />标记内的<tx:method />标记的各种属性:
属性 | 是否必须? | 默认值 | 描述 |
---|---|---|---|
name | Yes | | 与事务属性关联的方法名称。 通配符()字符可用于将相同的事务属性设置与多个方法相关联(例如,get ,handle ,on Event等)。 |
propagation | No | REQUIRED | 事务传播行为。 |
isolation | No | DEFAULT | 事务隔离级别。 仅适用于REQUIRED或REQUIRES_NEW的传播设置。 |
timeout | No | -1 | 事务超时(秒)。 仅适用于传播REQUIRED或REQUIRES_NEW。 |
read-only | No | false | 读写与只读事务。 仅适用于REQUIRED或REQUIRES_NEW。 |
rollback-for | No | | 以逗号分隔的触发回滚的异常实例列表。 例如,com.foo.MyBusinessException,ServletException。 |
no-rollback-for | No | | 以逗号分隔的不触发回滚的异常实例列表。 例如,com.foo.MyBusinessException,ServletException。 |
除了基于XML的事务配置声明方法之外,你还可以使用基于注解的方法。 直接在Java源代码中声明事务语义会使声明更接近受影响的代码。 不存在过度耦合的危险,因为无论如何,用于 事务处理的代码几乎总是以这种方式部署。
标准的javax.transaction.Transactional注解也被支持作为Spring自己的注解的替代品。 有关更多详细信息,请参阅JTA 1.2文档。
使用@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);