@Transactional注解及其支持类所提供的功能最低要求使用Java 5(Tiger)。
除了基于XML文件的声明式事务配置外,你也可以采用基于注解式的事务配置方法。直接在Java源代码中声明事务语义的做法让事务声明和将受其影响的代码距离更近了,而且一般来说不会有不恰当的耦合的风险,因为,使用事务性的代码几乎总是被部署在事务环境中。
下面的例子很好地演示了@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); void insertFoo(Foo foo); void updateFoo(Foo foo); }
当上述的POJO定义在Spring IoC容器里时,上述bean实例仅仅通过一 行xml配置就可以使它具有事务性的。如下:
<!-- 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-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"> <!-- 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"/> <!-- aPlatformTransactionManageris 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>
实际上,如果你用'transactionManager'来定义PlatformTransactionManagerbean的名字的话,你就可以忽略<tx:annotation-driven/>标签里的'transaction-manager'属性。 如果PlatformTransactionManagerbean你要通过其它名称来注入的话,你必须用'transaction-manager'属性来指定它,如上所示。
方法的可见度和@Transactional
@Transactional注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用@Transactional注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
@Transactional注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。然而,请注意仅仅@Transactional注解的出现不足于开启事务行为,它仅仅 是一种元数据,能够被可以识别@Transactional注解和上述的配置适当的具有事务行为的beans所使用。上面的例子中,其实正是<tx:annotation-driven/>元素的出现 开启 了事务行为。
Spring团队的建议是你在具体的类(或类的方法)上使用@Transactional注解,而不要使用在类所要实现的任何接口上。你当然可以在接口上使用@Transactional注解,但是这将只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装(将被确认为严重的)。因此,请接受Spring团队的建议并且在具体的类上使用@Transactional注解。
当使用@Transactional风格的进行声明式事务定义时,你可以通过<tx:annotation-driven/>元素的 "proxy-target-class" 属性值来控制是基于接口的还是基于类的代理被创建。如果 "proxy-target-class" 属值被设置为 "true",那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 "proxy-target-class" 属值被设置为 "false" 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。
在多数情形下,方法的事务设置将被优先执行。在下列情况下,例如:DefaultFooService类被注解为只读事务,但是,这个类中的updateFoo(Foo)方法的@Transactional注解的事务设置将优先于类级别注解的事务设置。
@Transactional(readOnly = true) public class DefaultFooService implements FooService { public Foo getFoo(String fooName) { // do something } // these settings have precedence for this method @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) public void updateFoo(Foo foo) { // do something } }
@Transactional注解是用来指定接口、类或方法必须拥有事务语义的元数据。 如:“当一个方法开始调用时就开启一个新的只读事务,并停止掉任何现存的事务”。 默认的@Transactional设置如下:
事务传播设置是PROPAGATION_REQUIRED
事务隔离级别是ISOLATION_DEFAULT
事务是 读/写
事务超时默认是依赖于事务系统的,或者事务超时没有被支持。
任何RuntimeException将触发事务回滚,但是任何 checkedException将不触发事务回滚
这些默认的设置当然也是可以被改变的。@Transactional注解的各种属性设置总结如下:
表 9.2. @Transactional注解的属性
属性 | 类型 | 描述 |
---|---|---|
传播性 | 枚举型:Propagation | 可选的传播性设置 |
隔离性 | 枚举型:Isolation | 可选的隔离性级别(默认值:ISOLATION_DEFAULT) |
只读性 | 布尔型 | 读写型事务 vs. 只读型事务 |
超时 | int型(以秒为单位) | 事务超时 |
回滚异常类(rollbackFor) | 一组Class类的实例,必须是Throwable的子类 | 一组异常类,遇到时 必须 进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。 |
回滚异常类名(rollbackForClassname) | 一组Class类的名字,必须是Throwable的子类 | 一组异常类名,遇到时 必须 进行回滚 |
不回滚异常类(noRollbackFor) | 一组Class类的实例,必须是Throwable的子类 | 一组异常类,遇到时 必须不 回滚。 |
不回滚异常类名(noRollbackForClassname) | 一组Class类的名字,必须是Throwable的子类 | 一组异常类,遇到时 必须不 回滚 |
考虑这样的情况,你有一个类的实例,而且希望 同时插入事务性通知(advice)和一些简单的剖析(profiling)通知。那么,在<tx:annotation-driven/>环境中该怎么做?
我们调用updateFoo(Foo)方法时希望这样:
配置的剖析切面(profiling aspect)开始启动,
然后进入事务通知(根据配置创建一个新事务或加入一个已经存在的事务),
然后执行原始对象的方法,
然后事务提交(我们假定这里一切正常),
最后剖析切面报告整个事务方法执行过程花了多少时间。
这章不是专门讲述AOP的任何细节(除了应用于事务方面的之外)。请参考 第 6 章 使用Spring进行面向切面编程(AOP) 章以获得对各种AOP配置及其一般概念的详细叙述。
这里有一份简单的剖析切面(profiling aspect)的代码。(注意,通知的顺序是由Ordered接口来控制的。要想了解更多细节,请参考 第 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 aDataSourceand aPlatformTransactionManagerhere --> </beans>
上面配置的结果是创建了一个'fooService'bean,剖析方面和事务方面被 依照顺序 施加其上。如果我们希望剖析通知在目标方法执行之前 后于 事务通知执行,而且在目标方法执行之后 先于 事务通知,我们可以简单地交换两个通知bean的order值。
如果配置中包含更多的方面,它们将以同样的方式受到影响。
通过AspectJ切面,你也可以在Spring容器之外使用Spring框架的@Transactional功能。要使用这项功能你必须先给相应的类和方法加上@Transactional注解,然后把spring-aspects.jar文件中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect切面连接进(织入)你的应用。同样,该切面必须配置一个事务管理器。你当然可以通过Spring框架容器来处理注入,但因为我们这里关注于在Spring容器之外运行应用,我们将向你展示如何通过手动书写代码来完成。
在我们继续之前,你可能需要好好读一下前面的第 9.5.6 节 “使用@Transactional” 和 第 6 章 使用Spring进行面向切面编程(AOP) 两章。
// construct an appropriate transaction manager DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource()); // configure theAnnotationTransactionAspectto use it; this must be done before executing any transactional methods AnnotationTransactionAspect.aspectOf().setTransactionManager (txManager);
使用此切面(aspect),你必须在 实现 类(和/或类里的方法)、而 不是 类的任何所实现的接口上面进行注解。AspectJ遵循Java的接口上的注解 不被继承 的规则。
类上的@Transactional注解指定了类里的任何 public 方法执行的默认事务语义。
类里的方法的@Transactional将覆盖掉类注解的默认事务语义(如何存在的话)。public、protected和默认可见的方法可能都被注解。直接对protected和默认可见的方法进行注解,让这些方法在执行时去获取所定义的事务划分是唯一的途径。
要把AnnotationTransactionAspect织入你的应用,你或者基于AspectJ构建你的应用(参考 AspectJ Development Guide),或者采取“载入时织入”(load-time weaving),参考 第 6.8.4 节 “在Spring应用中使用AspectJ Load-time weaving(LTW)” 获得关于使用AspectJ进行“载入时织入”的讨论。