声明式事务管理是spring对事务管理的最常用的方式,因为这种方式对代码的影响最小,因此也符合非侵入性的轻量级容器的概念。Spring的事务管理是通过AOP的方式来实现的,因为事务方面的代码与spring的绑定并以一种样板式结构使用。在理解spring声明式事务管理我们首先要理解他是通过AOP怎么具体实现的。其中的事务通知由元数据(目前基于xml和注解)驱动。代理对象由元数据结合产生一个新的代理对象。他使用一个PlatformTransactionManager实现配合TransactionInterceptor在方法调用之前实施事务。下面我们就通过一个图来看一下spring声明式事务管理的执行过程。
下面我们就以一个spring官方文档所给的例子来具体看一下用xml配置方式怎么来实现声明式事务管理:
首先请看下面的接口和它的实现。这个例子的意图是介绍概念:
// 我们想做成事务性的服务接口
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(); }}
首先要解释的是很多同学可能都在考虑这个事务管理到底是放在dao层还是放在service层呢。这个问题我想大多数童鞋的反应应该都是在dao层上吧,刚开始我也是这么想的。但是大家想想,如果我们要进行两个甚至多个dao层中的方法操作,并且要求放在同一个事务里时,我们该怎么来管理这个事务呢,这时我们就没办法了。所以我们应该把事务管理放在service层中,我们直接在service层中调用这两个dao层的方法就oK了。
下面我们接着往下看,我们假定,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-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.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' 开头的方法看做执行在只读事务上下文中, 其余的方法执行在默认语义的事务上下文中”。 其中的 'transaction-manager' 属性被设置为一个指向 PlatformTransactionManager bean的名字(这里指 'txManager'), 该bean将会真正管理事务。配置中最后一段是 <aop:config/> 的定义, 它确保由 'txAdvice' bean定义的事务通知在应用中合适的点被执行。 首先我们定义了 一个切面,它匹配 FooService 接口定义的所有操作, 我们把该切面叫做 'fooServiceOperation'。然后我们用一个通知器(advisor)把这个切面与 'txAdvice' 绑定在一起, 表示当 'fooServiceOperation' 执行时,'txAdvice' 定义的通知逻辑将被执行。
好了,上面就是官方文档给出的这个配置文件的解释,不知道大家有没有看懂,反正对于初学者我的时候,我是真没看懂,不太容易懂,当然了,大牛们是一定能看懂的。下面我就根据我自己的理解来通俗的讲解一下。
首先我们应该要把服务对象'fooService' 声明成一个bean,我们要把一个服务对象('fooService' bean)做成事务性的。我们就应该首先在声明一个事务管理的建议,用什么来管理,spring给我们提供了事务封装,这个就封装在了<tx:advice/>中,这个事务建议给我们提供了一个transaction-manager属性,用他可以指定我们用谁来管理我们的事务。我们上边的例子用的为一个指向 PlatformTransactionManager bean的名字(这里指 'txManager'), 该bean将会真正管理事务。上面用的事务管理类是用的jdbc中提供的事务管理,当然这里也可以指定为hibernate管理。当然了,不管用那个类来管理我们的事务,都不要忘记了提供我们的datasource属性,因为事务管理也需要这里面的信息。我们声明好事务建议,也指定好了具体用哪个类来管理了,下面我们的任务就是要把我们定义好的这些利用AOP把我们的事务管理织入到我们的业务逻辑里面了。<aop:config/> 的定义, 它确保由 'txAdvice' bean定义的事务通知在应用中合适的点被执行。 首先我们定义了 一个切面,它匹配 FooService 接口定义的所有操作, 我们把该切面叫做 'fooServiceOperation'。<aop:pointcut/> 元素定义是AspectJ的切面表示法,上述表示x.y.service.FooService包下的任意方法。然后我们用一个通知器(advisor)把这个切面与 'txAdvice' 绑定在一起, 表示当 'fooServiceOperation' 执行时,'txAdvice' 定义的通知逻辑将被执行。大体流程就是这样的了。
上面的配置将为'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()); }}
运行可以清楚的看到如下结果:
- Invoking rollback for transaction on x.y.service.FooService.insertFoo
due to throwable [java.lang.UnsupportedOperationException]
<tx:advice/> 有关的设置
通过 <tx:advice/> 标签来指定不同的事务性设置。默认的 <tx:advice/> 设置如下:
事务传播设置是 REQUIRED
隔离级别是DEFAULT
事务是 读/写
事务超时默认是依赖于事务系统的,或者事务超时没有被支持。
任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚
这些默认的设置当然也是可以被改变的。 <tx:advice/> 和 <tx:attributes/> 标签里的 <tx:method/> 各种属性设置总结如下:
Table 9.1. <tx:method/> 有关的设置
属性 |
是否需要? |
默认值 |
描述 |
name |
是 |
与事务属性关联的方法名。通配符(*)可以用来指定一批关联到相同的事务属性的方法。 如:'get*'、'handle*'、'on*Event'等等。 |
|
propagation |
不 |
REQUIRED |
事务传播行为 |
isolation |
不 |
DEFAULT |
事务隔离级别 |
timeout |
不 |
-1 |
事务超时的时间(以秒为单位) |
read-only |
不 |
false |
事务是否只读? |
rollback-for |
不 |
将被触发进行回滚的 Exception(s);以逗号分开。 如:'com.foo.MyBusinessException,ServletException' |
|
no-rollback-for |
不 |
不 被触发进行回滚的 Exception(s);以逗号分开。 如:'com.foo.MyBusinessException,ServletException' |
下面我们具体来看一下事务的传播性的几个值:
REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。
NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。
REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。
MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。
SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。
NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务 拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
使用 @Transactional
除了基于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配置就可以使它具有事务性的。如下:
<?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.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.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>
在多数情形下,方法的事务设置将被优先执行。在下列情况下,例如: 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 注解是用来指定接口、类或方法必须拥有事务语义的元数据。 如:“当一个方法开始调用时就开启一个新的只读事务,并停止掉任何现存的事务”。 默认的 @Transactional 设置如下:
事务传播设置是 PROPAGATION_REQUIRED
事务隔离级别是 ISOLATION_DEFAULT
事务是 读/写
事务超时默认是依赖于事务系统的,或者事务超时没有被支持。
任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚
这些默认的设置当然也是可以被改变的。 @Transactional 注解的各种属性设置总结如下:
@Transactional 注解的属性
属性 |
类型 |
描述 |
propagation |
枚举型:Propagation |
可选的传播性设置 |
isolation |
枚举型:Isolation |
可选的隔离性级别(默认值:ISOLATION_DEFAULT) |
readOnly |
布尔型 |
读写型事务 vs. 只读型事务 |
timeout |
int型(以秒为单位) |
事务超时 |
rollbackFor |
一组 Class 类的实例,必须是Throwable 的子类 |
一组异常类,遇到时 必须 进行回滚。默认情况下checked exceptions不进行回滚,仅unchecked exceptions(即RuntimeException的子类)才进行事务回滚。 |
rollbackForClassname |
一组 Class 类的名字,必须是Throwable的子类 |
一组异常类名,遇到时 必须 进行回滚 |
noRollbackFor |
一组 Class 类的实例,必须是Throwable 的子类 |
一组异常类,遇到时 必须不 回滚。 |
noRollbackForClassname |
一组 Class 类的名字,必须是Throwable 的子类 |
一组异常类,遇到时 必须不 回滚 |
在写代码的时候,不可能对事务的名字有个很清晰的认识,这里的名字是指会在事务监视器(比如WebLogic的事务管理器)或者日志输出中显示的名字, 对于声明式的事务设置,事务名字总是全限定名+"."+事务通知的类的方法名。比如BusinessService类的handlePayment(..)方法启动了一个事务,事务的名称是:
com.foo.BusinessService.handlePayment