Spring事务王国概览

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring事务王国概览相关的知识,希望对你有一定的参考价值。

Spring事务王国概览


事务就是一个原子性操作,该操作内部的一组命令,要么都执行成功,要么都执行失败


事务家族

事务的大家族中常会出现下面几个重要的家庭成员:

  • Resource Manager: 简称RM,可以将其看做是数据的管理员,例如,数据库服务器(mysql),消息服务器等
  • Transaction Processing Monitor: 简称TPM或者TP Monitor,他负责在分布式场景下协调多个RM的事务处理。
  • Transaction Manager: 简称TM,它可以认为是TP Monitor的核心模块,直接负责多RM之间事务处理的协调工作,并提供事务界定,事务上下文传播等功能接口。

我们通常会按照事务中涉及的RM的多寡将事务分为两类,即全局事务和局部事务.


全局事务

如果整个事务处理过程中存在多个RM,那么就需要通过TP Monitor来协调多RM间的事务一致性。TP Monitor通过两阶段提交协议来确保整个事务的ACID属性。

通常这种场景下的事务,被称为全局事务或者分布式事务。


所有应用程序提交的事务请求,都需要通过TP Monitor的调配之后,直接由TM统一管理,TM将使用两阶段提交协议来协调多RM之间的事务处理。

两阶段提交的过程可类比如下场景:




但是如果其中一方不愿意呢?



只有在双方都确认的条件下面,整个事务才能顺利提交,否则一旦一方反悔,事务就必须回滚到之前的状态


局部事务

如果当前事务中只有一个RM参与其中,我们就可以称当前事务为局部事务。

比如,在当前事务中只对一个数据库进行更新,或者只向一个消息队列中发送消息的情况,都属于局部事务。


因为局部事务只存在一个RM,因此没有必要引入相应的TP Monitor来帮助协调管理多个RM之间的事务处理。

通常情况下,相应的RM内部都有内置的事务支持,所以,在局部事务中,我们更倾向于之间使用RM的内置事务支持。


注意

局部事务与全局事务的区分在于事务中涉及到的RM数量,而不是系统中实际有多少RM,因为即使系统中存在多个数据库(即RM),只要当前事务只更新一个数据库的数据,那么当前事务就应该算作局部事务,而不是全局事务。

对于局部事务而言,一般都直接使用RM内置的事务支持,当然也可以通过引入TP Monitor在分布式事务场景下进行事务管理(但是显然没必要)。

通过情况下,各个TP Monitor在实现的时候都会判断当前参与事务的RM数量,如果只有一个RM参与,那么会做一定优化处理,避免使用两阶段提交协议带来额外的性能损耗。


Java事务管理

Java平台的局部事务支持

在Java的局部事务场景中,系统里事务管理的具体处理方式,会随着所使用的数据访问技术的不同而各异。

我们不是使用专用的事务API来管理事务,而是通过当前使用的数据访问技术所提供的基于connection的api来管理事务。


要对数据库的访问过程中的事务进行管理,每种数据访问技术都提供了特定与它自身的事务管理API,比如JDBC是Java平台访问关系数据库最基础的API。

如果直接使用JDBC进行数据访问的话,我们可以将数据库连接的自动提交设置为false,改为手动提交来控制整个事务的提交或者回滚。

  public boolean giveMoney(String Giver,String Revicer,int money)  
           ....
           try
            conn = JDBCUtil.getConnection();
            //开启事务
            conn.setAutoCommit(false);
            ....
            //结束事务
            conn.commit();
            return true;
         catch (SQLException throwables) 
            //事务进行回滚
                conn.rollback();
       ....
    

过程大致如上


Java平台分布式事务支持

Java平台上的分布式事务管理,主要是通过JTA或者JCA提供支持的。

基于JTA的分布式事务管理

JTA是Sun公司提出的标准化分布式事务访问的Java接口规范。不过,JTA规范定义的知识一套Java的接口定义,具体的实现留给了相应的提供商去实现,各个Java EE应用服务器需要提供对JTA的支持。另外,除了可以使用绑定到Java EE应用服务器的JTA实现之外,Java平台也存在几个独立的并且比较成熟的JTA实现产品,包括:

  • JOTM
  • Atomikos
  • JBoss Transactions

基于JCA的分布式事务管理

JCA规范主要面向EIS的集成,通过为遗留的EIS系统和JAVA EE应用服务器指定统一的通信标准,二者可以实现各种服务上的互通。


Java事务支持的缺陷

  • 事务处理逻辑与业务代码耦合,导致事务管理代码在数据访问层和业务服务层的到处散落
  • 事务处理过程中抛出的异常不统一,应该抛出unchecked exception,但是有些事务管理代码抛出的依然是chcked exception,这将强制客户端代码捕捉并处理它。并且缺乏统一的事务异常管理体系。
  • 不同的数据源提供的事务管理api不同,因此需要一个更高级的抽象,帮助我们统一事务管理API。

显然,用过spring的小伙伴都体验过了spring的全套事务服务带来的舒爽体验,也解决了上面提到的这些问题,并且还更加强大,那么下面就来一起探究一下吧。


Spring事务王国

org.springframework.transaction.PlatformTransactionManager是Spring事务王国的核心接口,它规定了为应用程序提供事务界定的统一方式。

public interface PlatformTransactionManager extends TransactionManager 
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException;
	void commit(TransactionStatus status) throws TransactionException;
	void rollback(TransactionStatus status) throws TransactionException;

PlatformTransactionManager是整个事务抽象策略的顶层接口,Spring的事务框架针对不同的数据访问方式以及全局事务场景,提供相应的实现类。

如果让你给出一个PlatformTransactionManager的自定义实现类,how do you do it ?

这里先用JDBC数据访问方式的局部事务为例:

我们通常是将事务管理放在Service层,而将数据访问逻辑放在Dao层。这样可以提高数据访问逻辑的重用性,并且在Service层根据相应的逻辑来决定是提交或者回滚事务。

因为JDBC的局部事务控制是需要通过同一个Connection来完成的,因此要保证两个DAO的数据访问处于同一个事务中,就需要保证它们使用的是同一个connection。

要完成这点,通常我们会采用connection-passing的方式,即为同一个事务中的各个dao的数据访问方法传递当前的Connection。

  • 注意,事务开启前要取消当前连接的自动提交,事务结束后,要恢复当前连接的自动提交

上面这种事务管控方式最大的问题在于事务代码无法摆脱connection的束缚,导致connection与当前业务代码耦合

我们应该将事务过程中用到的Connection实例放置于一个统一地点,这样就解除了事务管理代码和数据访问代码直接通过connection的耦合。

有一个办法是: 事务开始前取得一个connection,然后将这个connection绑定到当前调用线程。之后,数据访问对象在使用connection进行数据访问的时候,就可以从当前线程上获得这个事务开始时候绑定的connection实例。

当所有的数据访问都完成后,我们就可以将connection提交或者回滚事务,然后解除它到当前线程的绑定。


如果要让我们来自定义一个存放当前线程绑定的conn的类,那么大概会长下面这样:

public class TransactionResourceManager 
    private static ThreadLocal resources=new ThreadLocal();

    public static Object getResource()
        return resources.get();
    

    public static void bindResource(Object resource)
        resources.set(resource);
    

    public static Object unbindResource()
        Object resource = getResource();
        resources.set(null);
        return resource;
    

对于我们要实现的针对JDBC的PlatformTransactionManager ,只需要在事务开始的时候,通过我们的TransactionResourceManager 将connection绑定到线程,然后再事务结束的时候解除绑定即可。

这里给出JdbcTransactionManager简单实现:

public class JdbcTransactionManager implements PlatformTransactionManager 
   private DataSource dataSource;

    public JdbcTransactionManager(DataSource dataSource) 
        this.dataSource = dataSource;
    

    @Override
    public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException 
        try 
            Connection connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            TransactionResourceManager.bindResource(connection);
            return new DefaultTransactionStatus(connection,true,true,false,true,null);
         catch (SQLException e) 
            throw new CannotCreateTransactionException("cannot get connection for tx",e);
        
        
    


    @Override
    public void commit(TransactionStatus status) throws TransactionException 
        Connection connection = (Connection) TransactionResourceManager.unbindResource();
        try 
            connection.commit();
         catch (SQLException e) 
            throw new UnexpectedRollbackException("commit failed with SQLex",e);
        finally 
            try 
                connection.close();
             catch (SQLException e) 
                e.printStackTrace();
            
        
    


    @Override
    public void rollback(TransactionStatus status) throws TransactionException 
        Connection connection = (Connection) TransactionResourceManager.unbindResource();
        try 
            connection.rollback();
         catch (SQLException e) 
            throw new UnexpectedRollbackException("rollback failed with SQLex",e);
        finally 
            try 
                connection.close();
             catch (SQLException e) 
                e.printStackTrace();
            
        
    


当然上面给出的是简单的实现,spring可没有那么容易让你看透,上面给出的代码还存在以下问题:

  • 如何确保PlatformTransactionManager中方法按序调用,而不会因为错误或者没有调用相关方法,导致资源泄露和事务管理代码混乱。
  • 如果某个数据访问对象不需要上面提供的事务支持,那么该如何获得connection进行数据访问呢?

spring提供的DataSourceUtils类就是用来完成对connection的管理,DataSourceUtils会从类似TransactionResourceManager 的类中获取connection资源,如果当前线程之前没有绑定任何connection,那么它就通过数据访问对象的DataSource引用获取新的connection,否则就是要绑定的那个Connection。

这就是为什么要强调,当我们使用spring提供的事务支持的时候,必须通过DataSourceUtils来获取连接,因为它提供了Spring事务管理框架在数据访问层需要提供的基础设置中不可或缺的一部分。


三大护法

Spring的事务王国,有三大护法,他们三个共同维护着事务的一致性,他们分别是:

  • PlatformTransactionManager: 负责界定事务边界
  • TransactionDefinition: 负责定义事务相关属性,包括隔离级别,传播行为
  • TransactionStatus: 记录从事务开启到事务结束期间的事务状态,我们也可以利用其对事务进行有限的控制

TransactionDefinition

TransactionDefinition主要定义了事务属性相关配置:

  • 事务的隔离级别
  • 事务的传播行为
  • 事务的超时时间
  • 是否为只读事务

TransactionDefinition内定义了如下五个常量用于标志可供选择的隔离级别:

  • ISOLATION_DEFAULT: 使用数据库默认的隔离级别
  • ISOLATION_READ_UNCOMMITTED: 对应read uncommited隔离级别,无法避免脏读,不可重复读和幻读。
  • ISOLATION_READ_COMMITTED: 对应read commited隔离级别,可以避免脏读,但无法避免不可重复读和幻读。
  • ISOLATION_REPEATABLE_READ: 对应repeatable read隔离级别,可以避免脏读和不可重复读,但不能避免幻读。
  • ISOLATION_REPEATABLE_READ: 对应serializable隔离级别,可以避免所有的脏读,不可重复读和幻读

事务的传播行为涉及到一个事务在执行过程中,调用涉及其他事务时,相关事务的表现行为,如下图所示:

TransactionDefinition为事务的传播行为定义了下面几种选择:

  • PROPAGATION_REQUIRED: 如果当前存在一个事务,则加入当前事务,如果不存在任何事务,则创建一个新的事务。总之,要至少确保在一个事务中运行。并且此传播行为也是默认的事务传播行为。
  • PROPAGATION_SUPPORTS: 如果当前存在一个事务,则加入当前事务,如果不存在事务,则直接执行。对于一些查询方法来说,PROPAGATION_SUPPORTS通过是比较合适的传播行为选择。
  • PROPAGATION_MANDATORY:强制要求当前存在一个事务,如果不存在,则抛出异常。如果某个方法需要事务支持,但自身又不管理事务提交或者回滚,那么比较适合PROPAGATION_MANDATORY。
  • PROPAGATION_REQUIRES_NEW: 不管当前是否存在事务,都会创建新的事务。如果当前存在事务,会将当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED: 不支持当前事务,而是在没有事务的情况下才会执行,如果当前存在事务,当前事务会被挂起
  • PROPAGATION_NEVER:永远不需要当前存在事务,如果存在事务,则抛出异常。
  • PROPAGATION_NESTED:如果存在当前事务,则在当前事务的一个嵌套事务中执行,否则与PROPAGATION_REQUIRED行为类似,即创建新事务,在新创建的事务中执行。

注意区分PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW:

  • PROPAGATION_REQUIRES_NEW创建的新事务与外层事务属于同一个档次,即二者的地位是相同的。当新创建的事务运行时,外层事务将被挂起。
  • 而PROPAGATION_NESTED创建的嵌套事务则不一样,他是寄生于当前外层事务的,它的地位比当前外层事务的地位要小一号。当内部嵌套事务运行的时候,外层事务也是处理active状态。

TransactionDefinition提供了TIMEOUT_DEFAULT常量定义,用来指定事务的超时时间。该值默认为-1,这会采用当前事务系统默认的超时时间,如果 底层的transaction manager不支持TIMEOUT_DEFAULT,那么必须将TIMEOUT_DEFAULT设置为-1,否则会抛出异常

TransactionDefinition提供的最后一个功能是是否将要创建一个只读的事务,如果 底层的transaction manager不支持只读事务,那么就不会理睬该设定

基本的事务属性我都在底层接口规定好,但是当我们需要切换不同规定底层实现时,底层的transaction manager不一定支持所有的属性,也可能有自己特有的属性控制


TransactionDefinition继承结构图如下:

编程式事务侧重于通过程序员的编码方式来处理事务,而声明式事务则侧重于通过一个aop切面加上声明式事务提供的事务属性,来暗地中处理事务,程序员只需要指定相关事务属性即可。


DefaultTransactionDefinition

DefaultTransactionDefinition是其默认实现,他提供了各种事务属性的默认值,并且通过它的setter方法,我们可以更改这些默认值。

这些默认值包括:

	private int propagationBehavior = PROPAGATION_REQUIRED;

	private int isolationLevel = ISOLATION_DEFAULT;

	private int timeout = TIMEOUT_DEFAULT;

	private boolean readOnly = false;

TransactionTemplate

TransactionTemplate是Spring提供的进行编程式事务管理的模板方法类,他直接继承了DefaultTransactionDefinition。

TransactionTemplate的设计思想和JDBCTemplate一致,是通过模板方法加回调接口的方式,将通用的事务处理代码,和资源管理封装为模板方法,而将需要变化的,并且需要被事务包裹的代码,以回调接口的形式传递出去。

该类中有两个核心方法,如下:

//TransactionCallback就是上面讲到的需要动态变化的回调接口,该接口内部封装了相关业务代码
	public <T> T execute(TransactionCallback<T> action) throws TransactionException 
		Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
        //CallbackPreferringPlatformTransactionManager是一个函数式接口,提供了一个execute方法
        //这里直接在该回调接口内部完成完全事务管理工作,然后返回结果
		if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) 
			return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
		
		//进入模板方法阶段--------->
		else 
		    //从事务管理器中拿到封装有当前事务状态的类
			TransactionStatus status = this.transactionManager.getTransaction(this);
			T result;
			try 
			//回调接口执行,但是会传入事务状态,根据事务的相关状态: 事务传播行为,事务隔离级别,事务超时时间等
			//进行处理--具体要不要开启事务,是这里决定的
				result = action.doInTransaction(status);
			
			//出现异常: 回滚
			catch (RuntimeException | Error ex) 
				// Transactional code threw application exception -> rollback
				rollbackOnException(status, ex);
				throw ex;
			
			catch (Throwable ex) 
				// Transactional code threw unexpected exception -> rollback
				rollbackOnException(status, ex);
				throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
			
			// 如果没有出现异常,就提交事务,然后返回处理结果
			this.transactionManager.commit(status);
			return result;
		
	

那么再来看看事务回滚这边是怎么写的:

private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException 
		Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

		logger.debug("Initiating transaction rollback on application exception", ex);
		try 
		//通过事务管理器根据事务状态回滚事务
			this.transactionManager.rollback(status);
		
		catch (TransactionSystemException ex2) 
			logger.error("Application exception overridden by rollback exception", ex);
			ex2.initApplicationException(ex);
			throw ex2;
		
		catch (RuntimeException | Error ex2) 
			logger.error("Application exception overridden by rollback exception", ex);
			throw ex2;
		
	

TransactionAttribute

TransactionAttribute是继承自TransactionDefinition的接口定义,主要面向使用Spring AOP进行声明式事务管理的场合,它新增了一个rollbackon方法。

	/**
	 * 
	 */
	boolean rollbackOn(Throwable ex);

spring 3.0和spring 5.0之后有新增了两个方法:

	/**
	 * Return a qualifier value associated with this transaction attribute.
	 * This may be used for choosing a corresponding transaction manager
	 * to process this specific transaction.
	 * 用来挑选一个事务管理器来执行当前事务
	 */
	@Nullable
	String getQualifier();

	/**
	 * Return labels associated with this transaction attribute.
	 * This may be used for applying specific transactional behavior
	 * or follow a purely descriptive nature.
	 * 通过标签属性,可以应用具体的事务行为
	 */
	Collection<String> getLabels();

DefaultTransactionAttribute

DefaultTransactionAttribute是TransactionAttribute的默认实现,他同时继承了DefaultTransactionDefinition。

它内部有四个属性,如下:

    //描述符
	@Nullable
	private String descriptor;
    //超时时间的设置
	@Nullable
	private String timeoutString;
    //用于匹配事务管理器
	@Nullable
	private String qualifier;
    //标签
	private Collection<String> labels = Collections.emptyList();

在DefaultTransactionDefinition的基础上新增了rollbackon的实现。DefaultTransactionDefinition的实现指定了,当异常类型为unchecked exception的情况下将会回滚事务。

	@Override
	public boolean rollbackOn(Throwable ex) 
		return (ex instanceof RuntimeException || ex instanceof Error);
	

DefaultTransactionAttribute下有两个实现类:

  • RuleBasedTransactionAttribute : 可以同时指定多个过滤规则,这些规则包含:RollbackRuleAttribute或者NoRollbackRuleAttribute的List形式提供。
  • DelegatingTransactionAttribute: rollback将使用传入的异常类型与这些回滚规则进行匹配,然后再决定是否要回滚事务

DelegatingTransactionAttribute是抽象类,它会将所有方法的调用委派给另一个具体的TransactionAttribute实现类,比如DefaultTransactionAttribute或者RuleBasedTransactionAttribute 。

public abstract class DelegatingTransactionAttribute extends DelegatingTransactionDefinition
		implements TransactionAttribute, Serializable 

	private final TransactionAttribute targetAttribute;

	public DelegatingTransactionAttribute(TransactionAttribute targetAttribute) 
		super(targetAttribute);
		this.targetAttribute = targetAttribute;
	


	@Override
	@Nullable
	public String getQualifier() 
		return this.targetAttribute.getQualifier();
	

	@Override
	public Collection<String> getLabels() 
		return this.targetAttribute.getLabels();
	

	@Override
	public boolean rollbackOn(Throwable ex) 
		return this.targetAttribute.rollbackOn(ex);
	



TransactionStatus

下面是TransactionStatus的类继承图:

从名字就可以推断出来,TransactionStatus是记录整个事务处理过程中的事务状态,更多的时候,我们将在编程式事务中使用该接口。

下面先来看看TransactionStatus继承的那些接口都提供了什么作用吧:

  • SavepointManager接口将回滚点的管理接口进行了统一,通过继承该接口,便具有了创建内部嵌套事务的能力
//一般事务执行完毕后,savepoint会被自动释放
//savepoint一般搭配嵌套事务作为传播行为使用
public interface SavepointManager 

	/**
	   在当前事务中,创建一个savepoint,如果发生异常,可以选择回滚到savepoint
	 */
	Object createSavepoint() throws TransactionException;

	/**
	   回滚到savepoint
	 */
	void rollbackToSavepoint(Object savepoint) throws TransactionException;

	/**
	   手动释放savepoint
	 */
	void releaseSavepoint(Object savepoint) throws TransactionException;



如果使用过mysql的savepoint的小伙伴应该知道上面再说什么,不知道的可以先去查资源


  • TransactionExecution 将事务执行过程中的状态接口进行了统一
public interface TransactionExecution 
	/**
	 * 是否是新事务,或者参与到一个现存的事务中,或者不参与任务事务
	 */
	boolean isNewTransaction();

	/**
	 * rollbackOnly标记当前事务让其进行回滚
	 */
	void setRollbackOnly();
	boolean isRollbackOnly();

	/**
	 * 当前事务是否结束了,即是否被提交或者回滚了
	 */
	boolean isCompleted();



  • TransactionStatus: 事务的各种状态信息记录,一个是事务执行过程中的状态记录,和回滚点相关状态记录
//Flushable接口就是写出数据
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable 

	/**
	   当前事务内部是否设置了savepoint,如果设置了说明当前事务是嵌套事务
	 */
	boolean hasSavep

以上是关于Spring事务王国概览的主要内容,如果未能解决你的问题,请参考以下文章

Spring繁华的AOP王国----第四讲

Spring事务管理---中

Spring繁华的AOP王国---第五讲之应用案例和扩展

Spring繁华的AOP王国---第一讲

Spring繁华的AOP王国---第二讲

Spring繁华的AOP王国---第二讲