SSH:Spring框架(声明式事务管理详解)

1)

概念:事务

(Transaction)

是访问并可能更新数据库中各种数据项的一个程序执行单元

(unit)

。事务通常由高级数据

库操纵语言或编程语言

(如

SQL

C++

Java

书写的用户程序的执行所引起,

并用形如

begin transaction

commit 

transaction

 

rollback 

transaction

语句(或函数调用)来界定。事务由事务开始

(begin 

transaction)

和事务结束

(commit 

transaction

 

rollback 

transaction)

之间执行的全体操作组成。

SQL 

Server

中事务语句开始或结束时

transaction

可简写为

tran

 

2

)特性:

 

事务是恢复和并发控制的基本单位。

 

事务应该具有

4

个属性:原子性、一致性、隔离性、持续性。这四个属性通常称为

ACID

特性。

 

原子性(

atomicity

。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

 

一致性(

consistency

。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相

关的。

 

隔离性(

isolation

。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务

是隔离的,并发执行的各个事务之间不能互相干扰。

 

持久性(

durability

。持续性也称永久性(

permanence

,指一个事务一旦提交,它对数据库中数据的改变就应该

是永久性的。接下来的其他操作或故障不应该对其有任何影响。

 

3

 

具体:如果在操作数据库是,需要操作数据库中的多张表,例如,主子表的关系已删除为例,再删除主表的

SQL

语句出错时,

就发生回滚,

不能执行删除字表的数据,

如果此处不用事务则会主表的数据未删除但是字表的数

据已经删除的错误,一般情况下适用于对数据库进行修改的操作!

 


声明式事务管理是spring对事务管理的最常用的方式,因为这种方式对代码的影响最小,因此也符合非侵入性的轻量级容器的概念。Spring的事务管理是通过AOP的方式来实现的,因为事务方面的代码与spring的绑定并以一种样板式结构使用。在理解spring声明式事务管理我们首先要理解他是通过AOP怎么具体实现的。其中的事务通知由元数据(目前基于xml和注解)驱动。代理对象由元数据结合产生一个新的代理对象。他使用一个PlatformTransactionManager实现配合TransactionInterceptor在方法调用之前实施事务。下面我们就通过一个图来看一下spring声明式事务管理的执行过程。

                                                   

下面我们就以一个spring官方文档所给的例子来具体看一下用xml配置方式怎么来实现声明式事务管理:


首先请看下面的接口和它的实现。这个例子的意图是介绍概念:

// 我们想做成事务性的服务接口

[java]  view plain copy print ?
  1. package x.y.service;  
  2. public interface FooService {  
  3.   Foo getFoo(String fooName);  
  4.   Foo getFoo(String fooName, String barName);  
  5.   void insertFoo(Foo foo);  
  6.   void updateFoo(Foo foo);  
  7. }  


// 上述接口的一个实现

[java]  view plain copy print ?
  1. package x.y.service;  
  2. public class DefaultFooService implements FooService {  
  3.   public Foo getFoo(String fooName) {  
  4.     throw new UnsupportedOperationException();  
  5.   }  
  6.   public Foo getFoo(String fooName, String barName) {  
  7.     throw new UnsupportedOperationException();  
  8.   }  
  9.   public void insertFoo(Foo foo) {  
  10.     throw new UnsupportedOperationException();  
  11.   }  
  12.   public void updateFoo(Foo foo) {  
  13.     throw new UnsupportedOperationException();  
  14.   }}  


       首先要解释的是很多同学可能都在考虑这个事务管理到底是放在dao层还是放在service层呢。这个问题我想大多数童鞋的反应应该都是在dao层上吧,刚开始我也是这么想的。但是大家想想,如果我们要进行两个甚至多个dao层中的方法操作,并且要求放在同一个事务里时,我们该怎么来管理这个事务呢,这时我们就没办法了。所以我们应该把事务管理放在service层中,我们直接在service层中调用这两个dao层的方法就oK了。

      下面我们接着往下看,我们假定,FooService的前两个方法(getFoo(String) 和getFoo(String, String))必须执行在只读事务上下文中,其他的方法(insertFoo(Foo)和 updateFoo(Foo))必须执行在可读写事务上下文中。我们根据这个要求来看一下配置文件,我们刚开始可能看不懂,不用慌,往下我们会一一解释的。

  1. <!-- from the file 'context.xml' -->  
  2. <?xml version="1.0" encoding="UTF-8"?>  
  3. <beans xmlns="http://www.springframework.org/schema/beans"  
  4.      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  5.      xmlns:aop="http://www.springframework.org/schema/aop"  
  6.      xmlns:tx="http://www.springframework.org/schema/tx"  
  7.      xsi:schemaLocation="  
  8.      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
  9.      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd  
  10.      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">  
  11.     
  12.   <!-- this is the service object that we want to make transactional -->  
  13.   <bean id="fooService" class="x.y.service.DefaultFooService"/>  
  14.   <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->  
  15.   <tx:advice id="txAdvice" transaction-manager="txManager">  
  16.   <!-- the transactional semantics... -->  
  17.   <tx:attributes>  
  18.     <!-- all methods starting with 'get' are read-only -->  
  19.     <tx:method name="get*" read-only="true"/>  
  20.     <!-- other methods use the default transaction settings (see below) -->  
  21.     <tx:method name="*"/>  
  22.   </tx:attributes>  
  23.   </tx:advice>  
  24.     
  25.   <!-- ensure that the above transactional advice runs for any execution  
  26.     of an operation defined by the FooService interface -->  
  27.   <aop:config>  
  28.   <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>  
  29.   <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>  
  30.   </aop:config>  
  31.     
  32.   <!-- don't forget the DataSource -->  
  33.   <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">  
  34.   <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>  
  35.   <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>  
  36.   <property name="username" value="scott"/>  
  37.   <property name="password" value="tiger"/>  
  38.   </bean>  
  39.   <!-- similarly, don't forget the PlatformTransactionManager -->  
  40.   <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  41.   <property name="dataSource" ref="dataSource"/>  
  42.   </bean>   
  43.   <!-- other <bean/> definitions here -->  
  44. </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创建一个代理对象,这个代理对象被装配了事务通知,所以当它的相应方法被调用时,一个事务将被启动、挂起、被标记为只读,或者其它(根据该方法所配置的事务语义)。

我们来看看下面的例子,测试一下上面的配置。

  1. public final class Boot {  
  2.   public static void main(final String[] args) throws Exception {  
  3.     ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);  
  4.     FooService fooService = (FooService) ctx.getBean("fooService");  
  5.     fooService.insertFoo (new Foo());  
  6.   }}  


运行可以清楚的看到如下结果:

- 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 注解的易用性,随后解释其中的细节。先看看其中的类定义:

  1. // the service class that we want to make transactional  
  2. @Transactional  
  3. public class DefaultFooService implements FooService {  
  4.   Foo getFoo(String fooName);  
  5.   Foo getFoo(String fooName, String barName);  
  6.   void insertFoo(Foo foo);  
  7.   void updateFoo(Foo foo);  
  8. }  


当上述的POJO定义在Spring IoC容器里时,上述bean实例仅仅通过一 行xml配置就可以使它具有事务性的。如下:

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4.      xmlns:aop="http://www.springframework.org/schema/aop"  
  5.      xmlns:tx="http://www.springframework.org/schema/tx"  
  6.      xsi:schemaLocation="  
  7.      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd  
  8.      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd  
  9.      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">  
  10.     
  11.   <bean id="fooService" class="x.y.service.DefaultFooService"/>  
  12.    <tx:annotation-driven transaction-manager="txManager"/>  
  13.    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
  14.    <property name="dataSource" ref="dataSource"/>  
  15.   </bean>  
  16. </beans>  



注意:  实际上,如果你用 'transactionManager' 来定义 PlatformTransactionManager bean的名字的话,你就可以忽略 <tx:annotation-driven/> 标签里的 'transaction-manager' 属性。 如果 PlatformTransactionManager bean你要通过其它名称来注入的话,你必须用 'transaction-manager' 属性来指定它。


      在多数情形下,方法的事务设置将被优先执行。在下列情况下,例如: DefaultFooService 类在类的级别上被注解为只读事务,但是,这个类中的 updateFoo(Foo) 方法的 @Transactional 注解的事务设置将优先于类级别注解的事务设置。

  1. @Transactional(readOnly = true)  
  2. public class DefaultFooService implements FooService {  
  3.   public Foo getFoo(String fooName) {  
  4.     // do something  
  5.   }  
  6.     // these settings have precedence for this method  
  7.     @Transactional(readOnly = falsepropagation = Propagation.REQUIRES_NEW)  
  8.     public void updateFoo(Foo foo) {  
  9.         // do something  
  10.          
  11.     }  
  12. }  


@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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值