Spring学习笔记(四)-- Spring事务全面分析

通过本系列的文章对Spring的介绍,我们对Spring的使用和两个核心功能IOC、AOP已经有了初步的了解,结合我个人工作的情况,由于项目是金融

统,那对事务的控制是必不可少的,并且是非常严格的控制。根据我对项目的研究,它在管理模块用的是JTA事务,而在交易模块用的是JDBC的事

务,但是,所有的这些事务的使用,都是用Spring封装后的编程式事务。我在看完《Spring In Action》后,在网上看了下大家对Spring事务的理解,貌

似都没有真正的文章是去全面剖析Spring对这些事务的支持,特此写下这篇博文,以分食众人。

提示本博文将讲授Spring事务的管理功能,包括编程式事务和声明式事务。经过对本博文的了解,你将可以理解Spring事务管理的实质,并合理运用

之。但是本博文假定您已经掌控了java的基本知识,并对Spring有一定的了解,如果您对Spring还不是很了解,请参考本系列的文章。同时您还需要具

有一定的事务管理的常识,如事务的边界、隔离的级别等等。本文将直接行使这些概念而不做具体说明。


Spring 事务管理最重要的API有三个: TransactionDefinition 、 PlatformTransactionManager 、 TransactionStatus。所谓事务管理,其实就是“按

给定的事务规则来执行提交或者回滚操作”。“给定的事务规则”就是用 TransactionDefinition 表示的,“按照……来执行提交或者回滚操作”便是用 

PlatformTransactionManager 来表示,TransactionStatus 用于表示一个运行着的事务的状态 。


Spring事务属性剖析

事务控制对企业系统而言至关主要。它担保了用户的每次操作都是可靠的,即便出现了异常情况,也不至于损坏后台数据的完性。就像银行的自助取

款机,常日都能正常为客户干事,然而也难免碰到操作过程中宕机的情形,此时,事务就必需确保出问题账户的操作不生效,就像用户刚才完全没

有使用过取款机一样,以担保用户和银行的利益都不受损失。在Spring中,事务是通过 TransactionDefinition 接口来定义的。该接口包含与事务属性有

关的方法,具体如清单1所示:

清单 1.TransactionDefinition 接口中定义的主要方法:

public interface TransactionDefinition{
int getIsolationLevel();//isolation 隔离级别
int getPropagationBehavior();//Propagation 传播行为
int getTimeout();//超时时间
boolean isReadOnly();//是否只读
}

在剖析第一个属性之前,我们有必要先了解一下几个概念脏读、不可重复读和幻读:

1. 脏读 ( 事务没提交,提前读取 ) :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一

个事务也访问这个数据,然后使用了这个数据

2. 不可重复读 ( 两次读的不一致 ) :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一

个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到

的数据不一样的,因此称为是不可重复读。

3. 幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二

个事务也修改这个表中的数据,这种修改是向表中插入新的一行数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据

行,就好象发生了幻觉一样。


接下来我们可以进入我们的第一个属性的剖析了:事务隔离级别。隔离级别是指若干个并发的事务之间的隔离程度。


TransactionDefinition 接口中定义了五个表示隔离级别的常量:

TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值

TransactionDefinition.ISOLATION_READ_COMMITTED 。

TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。

TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。

TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录

都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。

TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏

读、不可重复读和幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。


TransactionDefinition 定义中包括了如下几个表示传播行为的常量:

TransactionDefinition.PROPAGATION_REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事

TransactionDefinition.PROPAGATION_REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。

TransactionDefinition.PROPAGATION_SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

TransactionDefinition.PROPAGATION_NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。

TransactionDefinition.PROPAGATION_NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。

TransactionDefinition.PROPAGATION_MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

TransactionDefinition.PROPAGATION_NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取

值等价于 TransactionDefinition.PROPAGATION_REQUIRED。


这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而PROPAGATION_NESTED 是 Spring 所特有的。

以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话)。此时,内嵌事务并不是一个独立的事务,它依赖于外部事务

的存在,只有外部事务的提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点的概念,那嵌套事务就很容易理解

了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回

事务挂起

如:方法 A 支持事务,方法 B 不支持事务,方法 A 调用方法 B 。在方法 A 开始运行时,系统为它建立 Transaction 方法A中对于数据库的处理操

作,会在该Transaction 的控制之下。这时,方法 A 调用方法 B, 方法 A 打开的 Transaction 将挂起,方法B中任何数据库的操作,都不在该

Transaction 的管理之下。当方法 B 返回,方法 A 继续运行,之前的Transaction恢复,后面的数据库操作继续在该 Transaction 的控制之下提交或回

滚。

事务超时

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 

的值来表示超时时间,其单位是秒。

事务的只读属性

事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提

高事务处理的性能。在 TransactionDefinition中以boolean 类型来表示该事务是否只读。

事务的回滚规则

通常情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常),则默认将回滚事务;如果没有抛出任何异常,或者抛出了已检查

异常,则仍然提交事务。这通常也是大多数开发者希望的处理方式,也是 EJB 中的默认处理方式。但是,我们可以根据需要人为控制事务在抛出某些

未检查异常时仍然提交事务,或者在抛出某些已检查异常时回滚事务。


PlatformTransactionManager 

清单2.PlatformTransactionManager 接口中定义的主要方法:

Public interface PlatformTransactionManager{
  TransactionStatus getTransaction(TransactionDefinition definition)
   throws TransactionException;
   void commit(TransactionStatus status)throws TransactionException;
   void rollback(TransactionStatus status)throws TransactionException;
}

根据底层所使用的不同的持久化 API 或框架, PlatformTransactionManager 的主要实现类大致如下:

1. DataSourceTransactionManager :适用于使用 JDBC 和 iBatis 进行数据持久化操作的情况。

2. HibernateTransactionManager :适用于使用 Hibernate 进行数据持久化操作的情况。

3.JpaTransactionManager :适用于使用 JPA 进行数据持久化操作的情况。

4.另外还有 JtaTransactionManager 、 JdoTransactionManager 、 JmsTransactionManager等等。


如果我们使用 JTA 进行事务管理,我们可以通过 JNDI 和 Spring 的 JtaTransactionManager 来获取一个容器管理的

DataSourceJtaTransactionManager 不需要知道 DataSource 和其他特定的资源,因为它将使用容器提供的全局事务管理。而对于其他事务管理

器,比如DataSourceTransactionManager,在定义时需要提供底层的数据源作为其属性,也就是 DataSource 。与HibernateTransactionManager 对

应的是 SessionFactory ,与 JpaTransactionManager 对应的是EntityManagerFactory 等等。


TransactionStatus

PlatformTransactionManager.getTransaction( … ) 方法返回一个 TransactionStatus 对象。返回的TransactionStatus 对象可能代表一个新的或已经存

在的事务(如果在当前调用堆栈有一个符合条件的事务)。 TransactionStatus 接口提供了一个简单的控制事务执行和查询事务状态的方法。该接口定

义如清单3所示:

清单 3. TransactionStatus 接口中定义的主要方法

public  interface TransactionStatus{
   boolean isNewTransaction();
   void setRollbackOnly();
   boolean isRollbackOnly();
}

编程式事务管理

Spring的编程式事务管理概述

在Spring 出现以前,编程式事务管理对基于 POJO 的应用来说是唯一选择。用过 Hibernate 的人应该都知道,我们需要在代码中显式调用 beginTrans

action() 、 commit() 、 rollback() 等事务管理相关的方法,这就是编程式事务管理。通过 Spring 提供的事务管理 API ,我们可以在代码中灵活控制事

务的执行。在底层, Spring 仍然将事务操作委托给底层的持久化框架来执行。

基于底层API的编程式事务管理

根据 PlatformTransactionManager 、 TransactionDefinition 和 TransactionStatus 三个核心接口,我们完全可以通过编程的方式来进行事务管理。示

例代码如清单 4 所示(具体代码见我的java事务系列博文):

public class BankServiceImpl implements BankService {
private BankDao bankDao;
private TransactionDefinition txDefinition;
private PlatformTransactionManager txManager;
......
public boolean transfer(Long fromId , Long toId , double amount) {
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
boolean result = false;
try {
result = bankDao.transfer(fromId , toId , amount);
txManager.commit(txStatus);
} catch (Exception e) {
result = false;
txManager.rollback(txStatus);
System.out.println("Transfer Error!");
}
return result;
}
}

相应的配置文件如下:










如上所示,我们在类中增加了两个属性:一个是 TransactionDefinition 类型的属性,它用于定义一个事务,;另一个是 PlatformTransactionManager 类型的属性,用

于执行事务管理操作。如果方法需要实施事务管理,我们首先需要在方法开始执行前启动一个事务,调用PlatformTransactionManager.getTransactio

n(...) 方法便可启动一个事务。创建并启动了事务之后,便可以开始编写业务逻辑代码,然后在适当的地方执行事务的提交或者回滚。

基于TransactionTemplate的编程式事务

通过前面的示例可以发现,这种事务管理方式很容易理解,但令人头疼的是,事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且

每一个业务方法都包含了类似的启动事务、提交 / 回滚事务的样板代码幸运的是,Spring也意识到了这些问题,并提供了简化的方法,这就是Spring在

数据DAO层经常使用的模板回调方法,如清单6所示:

清单6. 基于 TransactionTemplate 的事务管理示例代码

public class BankServiceImpl implements BankService 
{
	private BankDao bankDao;
	private TransactionTemplate transactionTemplate;
	......
	public boolean transfer(final Long fromId, final Long toId, final double amount) 
	{
		return (Boolean) transactionTemplate.execute(new TransactionCallback()
		{
			public Object doInTransaction(TransactionStatus status) 
			{
				Object result;
				try {
					result = bankDao.transfer(fromId, toId, amount);
				} catch (Exception e) {
					status.setRollbackOnly();
					result = false;
					System.out.println("Transfer Error!");
				}
				return result;
			}
		});
	}
}

对应的bean配置实例如下:

清单 7. 基于 TransactionTemplate 的事务管理示例bean文件





由于目前我所开发的系统是金融系统,金融系统本身对事务的要求非常之高,不但要求能够精确控制事务的边界,它的开始、束甚

至在异常发生后的处理,都必须处于我们的控制之下(就一控制狂),所以我们项目选择的是Spring编程式事务的这种实现式。

声明式事务管理

Spring 的声明式事务管理概述

Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执

行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂

事务管理代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。因为事务管理本身

就是一个典型的横切逻辑,正是 AOP 的用武之地。Spring 开发团队也意识到了这一点,为声明式事务提供了简单而强大的支持。和编程式事务相比,

声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务的代码块级别。但是即便有这样的需求,也存在很多变通的

方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

下面就来看看 Spring 为我们提供的声明式事务管理功能。


基于TransactionProxyFactoryBean的声明式事务管理

清单 9. 基于 TransactionProxyFactoryBean 的事务管理示例配置文件


......
	
		
	
 
	
		
		
		
			
				PROPAGATION_REQUIRED 
			
		
	
......

显式为每一个业务类配置一个 TransactionProxyFactoryBean 的做法将使得代码显得过于刻板


基于 命名空间的声明式事务管理

前面那种声明式事务配置方式奠定了 Spring 声明式事务管理的基石。在此基础上,Spring 2.x 引入了命名空间,结合使用 命名空间,带给

开发人员配置声明式事务的全新体验,配置变得更加简单和灵活。另外,得益于 命名空间的切点表达式支持,声明式事务也变得更加强大


......
	
		
	
	
		
			
		
	
	
		
		
	
......

由于使用了切点表达式,我们就不需要针对每一个业务类创建一个代理对象了。另外,如果配置的事务管理器 Bean 的名字取值

“transactionManager ”,则我们可以省略的 transaction-manager 属性,因为该属性的默认值即为“ transactionManager ”

基于 @Transactional 的声明式事务管理

除了基于命名空间的事务配置方式, Spring 2.x 还引入了基于 Annotation 的方式,具体主要涉及 @Transactional 标注。@Transactional 可以作用于

接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来

覆盖类级别的定义。如清单 12 所示:

清单 12. 基于 @Transactional 的事务管理示例配置文件

@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId , Long toId , double amount) 
{
	return bankDao.transfer(fromId , toId , amount);
}

Spring 使用 BeanPostProcessor 来处理 Bean 中的标注,因此我们需要在配置文件中作如下声明来激活该后处理 Bean ,如清单 13  所示

清单 13. 启用后处理 Bean 的配置


与前面相似, transaction-manager 属性的默认值是 transactionManager ,如果事务管理器 Bean 的名字即为该值,则可以省略该属性。虽然 @Transactional 注解可以

作用于接口、接口方法、类以及类方法上,但是 Spring 小组建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会

生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected 、 private 或者默认可见

性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

基于 命名空间和基于 @Transactional 的事务声明方式各有优缺点。基于 的方式,其优点是与切点表达式结合,功能强大。利用切点表达

式,一个配置可以匹配多个方法,而基于@Transactional 的方式必须在每一个需要使用事务的方法或者类上用 @Transactional 标注,尽管可能大多

数事务的规则是一致的,但是对 @Transactional 而言,也无法重用,必须逐个指定。另一方面,基于 @Transactional 的方式使用起来非常简单明

了,没有学习成本。开发人员可以根据需要,任选其中一种使用,甚至也可以根据需要混合使用这两种方式。


如果不是对遗留代码进行维护,则不建议再使用基于 TransactionInterceptor 以及基于 TransactionProxyFactoryBean 的声明式事务管理方式,但是,

学习这两种方式非常有利于对底层实现的理解,由于篇幅过长,我只挑TransactionProxyFactoryBean来写

利用 TransactionProxyFactoryBean 生成事务代理




	
    	
    		
        		
            		com.mysql.jdbc.Driver
        		
            		jdbc:mysql://localhost:3306/spring
			
            		root
        		
            		32147
    		
    		
		
        		
            		
            		
            			
               	 			
                			Person.hbm.xml
            			
            		
            		
            			
				
            		org.hibernate.dialect.MySQLDialect
            		update
        	
        
    
    

            
            

 
    

        
        
        
            
            
                PROPAGATION_REQUIRED
                PROPAGATION_REQUIRED,readOnly
                PROPAGATION_REQUIRED
            
        
    


            
            
                personDao
            
            
        
        
        
            
                transactionInterceptor
                    
            
        
    
    
    
        


 大部分情况下,每个事务代理的事务属性大同小异,事务代理的实现类都是 TransactionProxyFactoryBean,事务代理 bean 都必须注入事务管理器。

对于这种情况, Spring 提供了 bean 与 bean 之间的继承,可以简化配置。将大部分的通用配置,配置成事务模板,而实际的事务代理 bean ,则继承

事务模板。这种配置方式可以减少部分配置代码,下面是采用继承的配置文件:






        

         PROPAGATION_REQUIRED,readOnly
         PROPAGATION_REQUIRED
    



    


    
        
     

      

这种配置方式,相比前面直接采用 TransactionProxyFactoryBean 的事务代理配置方式,可以大大减少配置文件的代码量。每个事务代理的

配置都继承事务模板,无需重复指定事务代理的实现类,无需重复指定事务传播属性——当然,如果新的事务代理有额外的事务属性,也可指定自己的

事务属性,此时,子 bean 的属性覆盖父 bean 的属性。当然每个事务代理 bean 都必须配置自己的目标 bean ,这不可避免。上面的配置可看出,事

务代理的配置依然是增量式的,每个事务代理都需要单独配置——虽然增量已经减少,但每个事务代理都需要单独配置。


结束语

虽然我很不愿意承认我对Spring事务的了解已经到此为止了,但是事实是如此。在结束之际,请允许我感谢开源社区的一篇文章,虽然里面内容写的乱

七八糟,但是它是我思路的来源:http://www.open-open.com/lib/view/open1414310646012.html


对Spring事务的总结如下:

1. 基于 TransactionDefinition、PlatformTransactionManager、TransactionStatus 编程式事务管理是Spring 提供的最原始的方式,通常我们不会这

写,但是了解这种方式对理解 Spring 事务管理的本质有很大作用。

2. 基于 TransactionTemplate 的编程式事务管理是对上一种方式的封装,使得编码更简单、清晰。对事务有着强烈的控制欲望的系统,可以选择这种

方式(我现在所在项目就是这种实现方式)

3. 基于 TransactionInterceptor 的声明式事务是 Spring 声明式事务的基础,通常也不建议使用这种方式,但是与前面一样,了解这种方式对理解 

Spring 声明式事务有很大作用。

4. 基于 TransactionProxyFactoryBean 的声明式事务是上中方式的改进版本,简化的配置文件的书写,这是Spring 早期推荐的声明式事务管理方式,

但是在 Spring 2.0 中已经不推荐了

5. 基于 命名空间的声明式事务管理是目前推荐的方式,其最大特点是与 Spring AOP 结合紧密,可以充分利用切点表达式的强大支持,

使得管理事务更加灵活。

6. 基于 @Transactional 的方式将声明式事务管理简化到了极致。开发人员只需在配置文件中加上一行启用相关后处理 Bean 的配置,然后在需要实施

事务管理的方法或者类上使用 @Transactional 指定事务规则即可实现事务管理,而且功能也不必其他方式逊色


你可能感兴趣的:(Spring学习笔记,java事务学习)