注意:大多数Spring框架使用者采用声明式事物管理。这样的选择对于应用程序代码有最少的影响,因此是非侵入式轻量级容器的不二选择。
Spring框架的声明式事物管理使得Spring的 AOP可用,虽然Spring框架中自带了事物方面代码并以样板文件方式使用,AOP概念一般不必充分利用这样的代码。
Spring框架的声明式事物管理与EJB CMT相似,其中你可以为每个方法指定事物行为。如果必要的话,可能在事物上下文中进行一个setRollbackOnly
()调用。两类事物管理的不同之处是:
- 不像EJB CMT ,与JTA关联,Spring框架的事物管理可以再任何环境中运行。其通过简单地修改配置文件可以使用JTA事物或者使用JDBC,JPA,Hibernate或JDO的本地事物。
- 可以在任何类中使用Spring框架的事物管理,而不像EJBs只能在指定的类中使用。
- Spring框架提供声明回滚机制,EJB没有这个功能。并支持编程式和声明式事物回滚
- Spring框架使你通过AOP可以自定义事物行为。例如,可以在事物回滚中插入自定义行为。你也可以与事物建议一起,添加任意建议。而EJB CMT,除非使用
setRollbackOnly
(),否则你不能影响容器的事物管理 - Spring框架在远程调用之间不支持事物上下文传递,犹如高端应用服务器做的那样。如果需要这个功能,建议使用EJB。然而,在使用这个功能前倾慎重考虑,因为一般地,人们不希望事物跨越远程调用。
回滚规则概念是很重要的:它们使得你能够指定哪个异常应该引起回滚。在配置中指定这个申明,而不是用Java代码。所以,虽然你可以仍旧在TransactionStatus
对象上调用setRollbackOnly
()回滚当前事物,但是一般地,你可以指定一个规则,MyApplicationException
必须总是回滚。这种选择的显著好处是业务对象不依赖事物底层。例如,它不需要导入Spring事物API或其他Spring API。
虽然EJB容器默认行为在系统异常时自动回滚事物(一般是运行期异常),但是EJB CMT在应用异常时(检查型异常)不自动回滚异常。然而Spring默认的声明式事物管理的行为延续了EJB的优点(在非检查型异常上,回滚是自动的),这对于自定义事物很有用。
11.5.1 理解Spring框架的声明式事物实现
仅仅用@Transactional
注解你的类是不够的,将@EnableTransactionManagement
添加到你的配置中,并稍后期望你能理解其如何工作的。这不得分描述了在事物关联问题中Spring框架的声明式事物底层的内部工作机理。
最重要的概念,除了Spring框架的声明式事物支持外还有借助AOP代理的支持,并且事物建议通过元数据驱动(目前是基于XML或者注解)。结合了事物元数据的AOP借助AOP代理使用一个TransactionInterceptor
,其与一个适当的PlatformTransactionManager
实现关联,来驱动方法调用周围的事物。
概念上讲,调用事物代理上的方法是像这样的:
11.5.2 声明式事物实现的例子
考虑下面的接口和旁边的实现。这个例子使用Foo和Bar类作为占位符,这样了你可以关注事物的使用而不是一个指定的领域模型。这个例子的目的是,DefaultFooService
类在每个实现方法体重抛出UnsupportedOperationException
实例,是很好的。允许你看见创建的事物并在响应UnsupportedOperationException
实例后回滚。
// 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);
}
// an implementation of the above interface
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
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>
检查前面的配置。你想要 一个业务对象,即fooService bean,事务性的。事物语法在<tx:advice/>
中定义。<tx:advice/>
定义以“所有以get为前缀的方法在只读事物的上下文中执行”,并且所有其他方法以默认的事物语法执行。<tx:advice/>
的transaction-manager
属性设置了PlatformTransactionManager
bean的名字,用于驱动事物,这里是txmanager bean。
注意:如果你想封装的PlatformTransactionManager
的名字有transactionManager
命名,你可以忽略事物建议<tx:advice/>
中的transaction-manager
属性。如果你要封装的PlatformTransactionManager
bean有其他名字,那么必须直接使用transaction-manager
属性,如前面例子所示的。
<aop:config/>
定义确保由txAdvice bean定义的事物建议在程序中在合适的时候执行。首先定义一个切点匹配FooService接口(fooServiceOperation
)中定义的任何操作的执行。之后使用一个advisor与txAdvice关联这个切点。结果指示在fooServiceOperation
执行点上,由txAdvice定义的建议将运行。
使用<aop:pointcut/>
元素定义的表达式是一个方面切点表达式;
一般要求是针对所有的业务层事物。最好的方式是仅改变切点表达式,来匹配你的业务层任何操作。例如:
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service..(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
注意:在这个例子中,假设你的业务接口定义在
x.y.service
包中。
现在我们分析了配置文件,那这些配置是做什么的?
上面的配置用于创建对象(由fooService bean定义创建)事物代理。这个代理将由事物建议配置,这样当在代理上执行一个合适的方法时,开启事物,挂起,标示为只读,等等,这取决于与这个方法关联的事物配置。考虑下面的程序测试驱动上述的配置。
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);
FooService fooService = (FooService) ctx.getBean("fooService");
fooService.insertFoo (new Foo());
}
}
运行上面的测试代码将打印出下面的日志。(Log4j输出和由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)
11.5.3 回滚一个声明式事物
前面部门概括了如何为类(在你的应用程序中一般是声明的业务层类)指定事物设置。这节讲述如何在一个简单的声明的例子中控制事物的回滚。
指向Spring框架事物底层(回滚事物)的推荐方式是从某个事物上下文当前执行的代码中抛出异常。Spring框架的事物底层代码将抓取任何未处理的异常,当其从栈中冒出来时,并作出决定是否标示为回滚事物。
你可以配置哪个异常类型标示为回滚事物,包括未检查型异常。下面的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框架的事物底层提交伴随的事物,即使面对一个未处理的
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框架的事物底层。
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
强烈建议尽可能的使用声明式事物方式回滚。如果确实不得已,也可以使用程序化回滚,但是了这样使用面对了一个纯净的基于POJO结构的窘境。
11.5.4 为不同的bean配置不同的事物语法
考虑这样的情景,你有大量的业务层对象,并且你想对每个对象应用一个完全不同的事物配置。通过定义明显的<aop:advisor/>
元素并使用不同的pointcut
和advice-ref
属性值可以达到这个要求。
为方便比较,首先假设你的业务层的所有类定义在一个根x.y.service
包中。为使得那个包和子包中定义的类的实例和以Service命名结尾的类的实例有默认的事物配置,你应该按照如下方式写:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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>
下面例子显示了如何使用完全不同的事物设置配置两个不同的beans
<?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>