考虑这样的情况,你有一个类的实例,而且希望 同时插入事务性通知(advice)和一些简单的剖析(profiling)通知。那么,在<tx:annotation-driven/>环境中该怎么做?
我们调用 updateFoo(Foo) 方法时希望这样:
-
配置的剖析切面(profiling aspect)开始启动,
-
然后进入事务通知(根据配置创建一个新事务或加入一个已经存在的事务),
-
然后执行原始对象的方法,
-
然后事务提交(我们假定这里一切正常),
-
最后剖析切面报告整个事务方法执行过程花了多少时间。
Note | |
---|---|
这章不是专门讲述AOP的任何细节(除了应用于事务方面的之外)。请参考 Chapter 6, 使用Spring进行面向切面编程(AOP) 章以获得对各种AOP配置及其一般概念的详细叙述。 |
这里有一份简单的剖析切面(profiling aspect)的代码。(注意,通知的顺序是由 Ordered 接口来控制的。要想了解更多细节,请参考 Section 6.2.4.7, “通知(Advice)顺序” 节。)
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; } }
这里是帮助满足我们上述要求的配置数据。
<?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-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.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"/> <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声明的方法来达到上面一样的设置效果。
<?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-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.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>
上面配置的结果是创建了一个 'fooService' bean,剖析方面和事务方面被 依照顺序 施加其上。如果我们希望剖析通知在目标方法执行之前 后于 事务通知执行,而且在目标方法执行之后先于 事务通知,我们可以简单地交换两个通知bean的order值。
如果配置中包含更多的方面,它们将以同样的方式受到影响。
通过AspectJ切面,你也可以在Spring容器之外使用Spring框架的 @Transactional 功能。要使用这项功能你必须先给相应的类和方法加上@Transactional注解,然后把 spring-aspects.jar 文件中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect 切面连接进(织入)你的应用。同样,该切面必须配置一个事务管理器。你当然可以通过Spring框架容器来处理注入,但因为我们这里关注于在Spring容器之外运行应用,我们将向你展示如何通过手动书写代码来完成。
Note | |
---|---|
在我们继续之前,你可能需要好好读一下前面的Section 9.5.6, “使用 @Transactional” 和Chapter 6, 使用Spring进行面向切面编程(AOP) 两章。 |
// 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);
Note | |
---|---|
使用此切面(aspect),你必须在 实现 类(和/或类里的方法)、而 不是 类的任何所实现的接口上面进行注解。AspectJ遵循Java的接口上的注解 不被继承 的规则。 |
类上的 @Transactional 注解指定了类里的任何 public 方法执行的默认事务语义。
类里的方法的 @Transactional 将覆盖掉类注解的默认事务语义(如何存在的话)。 public、protected和默认可见的方法可能都被注解。直接对 protected和默认可见的方法进行注解,让这些方法在执行时去获取所定义的事务划分是唯一的途径。
要把 AnnotationTransactionAspect 织入你的应用,你或者基于AspectJ构建你的应用(参考AspectJ Development Guide),或者采取“载入时织入”(load-time weaving),参考 Section 6.8.4, “在Spring应用中使用AspectJ Load-time weaving(LTW)” 获得关于使用AspectJ进行“载入时织入”的讨论。
Spring提供两种方式的编程式事务管理:
-
使用 TransactionTemplate
-
直接使用一个 PlatformTransactionManager 实现
如果你选择编程式事务管理,Spring小组推荐你采用第一种方法(即使用 TransactionTemplate)。第二种方法类似使用JTA的UserTransaction API (除了异常处理简单点儿)。
TransactionTemplate 采用与Spring中别的 模板 同样的方法,如 JdbcTemplate 和 HibernateTemplate。它使用回调机制,将应用代码从样板式的资源获取和释放代码中解放出来,不再有大量的try/catch/finally/try/catch代码块。同样,和别的模板类一样,TransactionTemplate 类的实例是线程安全的。
必须在事务上下文中执行的应用代码看起来像这样:(注意使用 TransactionCallback 可以有返回值)
Object result = tt.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { updateOperation1(); return resultOfUpdateOperation2(); } });
如果不需要返回值,更方便的方式是创建一个 TransactionCallbackWithoutResult 的匿名类,如下:
tt.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { updateOperation1(); updateOperation2(); } });
回调方法内的代码可以通过调用 TransactionStatus 对象的 setRollbackOnly() 方法来回滚事务。
想要使用 TransactionTemplate 的应用类必须能访问一个 PlatformTransactionManager(典型情况下通过依赖注入提供)。这样的类很容易做单元测试,只需要引入一个 PlatformTransactionManager 的伪类或桩类。这里没有JNDI查找、没有 静态 诡计,它是一个如此简单的接口。像往常一样,使用Spring给你的单元测试带来极大地简化。
你也可以直接使用 org.springframework.transaction.PlatformTransactionManager的实现来管理事务。只需通过bean引用简单地传入一个PlatformTransactionManager 实现,然后使用 TransactionDefinition 和 TransactionStatus 对象,你就可以启动一个事务,提交或回滚。
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
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中配置也不难。使用Spring,而不是EJB CMT,声明式事务管理在配置上的成本极大地降低了。
一般来说,Spring的事务抽象与应用服务器是无关的。另外,使用Spring的 JtaTransactionManager 类时,一种可选的方式是通过JNDI查询获得JTAUserTransaction 和 TransactionManager 对象,其中后者可以被设置为自动探测,这时针对不同的应用服务器有不同的方式。能够直接访问TransactionManager,确实在很大程度上增强了事务语义,可以参考 JtaTransactionManager 类的javadoc获得更多细节。
在一个使用WebLogic 7.0、8.1或更高版本的环境中,你一般会优先选用特定于WebLogic的 WebLogicJtaTransactionManager 类来取代基础的JtaTransactionManager 类。在WebLogic环境中,该类提供了对Spring事务定义的完全支持,超过了标准的JTA语义。它的特性包括:支持事务名,支持为每个事务定义隔离级别,以及在任何环境下正确地恢复事务的能力。
在WebSphere 5.1、5.0和4环境下,你可以使用Spring的 WebSphereTransactionManagerFactoryBean 类。这是一个工厂类,通过WebSphere的静态 访问方法获取到JTA TransactionManager 实例。(这些静态方法在每个版本的WebSphere中都不同。)一旦通过工厂bean获取到JTATransactionManager 实例,就可以使用该实例装配一个Spring的 JtaTransactionManager bean,它封装了JTA UserTransaction,提供增强的事务语义。请参考相关javadoc以获得完整信息。
开发者需要按照需求仔细地选择正确的 PlatformTransactionManager 实现。理解Spring的事务抽象如何与JTA全局事务一起工作是非常重要的。使用得当,就不会有任何冲突:Spring仅仅提供一个直观的、可移植的抽象层。
如果你使用全局事务,你 必须 为你的所有事务操作使用Spring的 org.springframework.transaction.jta.JtaTransactionManager 类(或 特定于某种应用服务器的子类)。否则Spring将试图在象容器数据源这样的资源上执行局部事务。这样的局部事务没有任何意义,好的应用服务器会把这些情况视为错误。
从下面的链接中你可以找到更多关于Spring框架事务支持方面的资源
-
来自 InfoQ 的 Java事务设计策略(Java Transaction Design Strategies) 是一本介绍Java事务方面相当好的书。同时它也包含了如何在Spring框架和EJB3中配置与使用事务的对比示例。
文章摘录自:http://www.redsaga.com/spring_ref/2.0/html/transaction.html