Spring事务-1

Spring事务

流程图说明

spring事务-详细

例子

  1. 配置类

    package com.lc.jdbc;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    import org.springframework.jdbc.datasource.SimpleDriverDataSource;
    import org.springframework.jdbc.datasource.embedded.DataSourceFactory;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    import javax.sql.DataSource;
    import java.sql.Driver;
    
    @Configuration
    @EnableTransactionManagement
    public class Config {
       @Bean
       public TestTx testTx() {
           return new TestTx();
       }
    
       @Bean
       public DriverManagerDataSource driverManagerDataSource() {
           DriverManagerDataSource dataSource = new DriverManagerDataSource();
           dataSource.setDriverClassName("com.mysql.jdbc.Driver");
           dataSource.setUrl("jdbc:mysql://localhost:3306/t_test");
           dataSource.setUsername("root");
           dataSource.setPassword("root");
           return dataSource;
       }
    
       @Bean
       public JdbcTemplate jdbcTemplate(DataSource dataSource) {
           JdbcTemplate jdbcTemplate = new JdbcTemplate();
           jdbcTemplate.setDataSource(dataSource);
           return jdbcTemplate;
       }
    
       @Bean
       public PlatformTransactionManager txManager(DataSource dataSource) {
           return new DataSourceTransactionManager(dataSource);
       }
    }
    
  2. 测试方法

    package transaction;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.util.Assert;
    
    public class TestTx {
    	@Autowired
    	private JdbcTemplate jdbcTemplate;
    	private int count;
    	@Autowired
    	private TestTx testTxAgent;
    
    	@Transactional(propagation = Propagation.REQUIRED)
    	public void updateNameWithException(int id, String name) {
    		jdbcTemplate.update("update t_test set name=? where id=?", name, id);
    		throw new RuntimeException();
    	}
    
    	public void updateNameWithNoromal(int id, String name) {
    		jdbcTemplate.update("update t_test set name=? where id=?", name, id);
    	}
    
    	@Transactional(propagation = Propagation.REQUIRED)
    	public TestBean getTestBeanById(int id) {
    		TestBean testBean = jdbcTemplate.queryForObject("select * from t_test where id=?", new BeanPropertyRowMapper<>(TestBean.class), id);
    		Assert.notNull(testBean, "testBean not be null");
    		try {
    			testTxAgent.updateNameWithException(id, testBean.getName() + "-" + count++);
    		}catch (Exception e){
    			e.printStackTrace();
    
    		}
    		updateNameWithNoromal(id,"init-1");
    		return testBean;
    	}
    
    }
    
    class TestBean {
    	private Integer id;
    	private String name;
    	private Double age;
    
    	public Integer getId() {
    		return id;
    	}
    
    	public void setId(Integer id) {
    		this.id = id;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public Double getAge() {
    		return age;
    	}
    
    	public void setAge(Double age) {
    		this.age = age;
    	}
    
    	@Override
    	public String toString() {
    		return "TestBean{" +
    				"id=" + id +
    				", name='" + name + '\'' +
    				", age=" + age +
    				'}';
    	}
    }
    
    
  3. 主启动类

    package com.lc.jdbc;
    
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.jdbc.core.ColumnMapRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    import org.springframework.jdbc.datasource.SimpleDriverDataSource;
    import org.springframework.jdbc.support.JdbcAccessor;
    import org.springframework.scripting.ScriptEvaluator;
    
    import java.sql.Connection;
    import java.sql.Driver;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.util.List;
    import java.util.Map;
    
    public class TestJdbMain {
       public static void main(String[] args) throws SQLException {
           try {
               AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
               TestTx testTx = context.getBean(TestTx.class);
               TestBean testBeanById = testTx.getTestBeanById(1);
               System.out.println(testBeanById);
           }catch (Throwable e){
               e.printStackTrace();;
           }
       }
    }
    
  4. 建表语句

    create table t_test
    (
       id   int auto_increment
           primary key,
       name varchar(32) null,
       age  double      null
    );
    

@EnableTransactionManagement解释说明

三个属性表示的意思都是和代理对象创建有关系,此外还得注意到,他导入了TransactionManagementConfigurationSelector

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

  /*
  1. 表示要强制走CGlib代理,true:走,false:会尝试使用JDK代理,如果JDK代理不能用的话,还是会走Cglib的代码
  2. 要注意,设置了这个会影响整个Spring代理对象的创建。在Spring中创建代理是通过 InfrastructureAdvisorAutoProxyCreator 来创建的
  他是全局的 
  */
	boolean proxyTargetClass() default false;

	/*
	表示采用哪种代理模式,PROXY为jdk,ASPECTJ为Aspect织入这其实对应的都是创建代理对象时候选择哪种方式属性
	*/
	AdviceMode mode() default AdviceMode.PROXY;
	/*
	表示顺序,多个事务的advisor执行到同一个方法上面,执行的顺序,
	不过,这个参数好像没有用到。
	*/
	int order() default Ordered.LOWEST_PRECEDENCE;

}

@EnableTransactionManagement干了什么事情?

都知道,spring的事务是通过AOP来实现的,同样的,@EnableCaching也是。在@EnableTransactionManagement里面肯定会导入一些和AOP,事务实现相关的类。

  1. 导入TransactionManagementConfigurationSelector,他本质上是一个ImportSelector,通过String[] selectImports(AnnotationMetadata)返回的全限定类名,可以解析这些类。

    在它里面,主要是通过mode()来确定那种那种事务模式。

    在这里插入图片描述
    )]

  2. 导入了事务的配置。ProxyTransactionManagementConfiguration

    可以看到,上面的代理里面配置了好几种,这里就着重分析ProxyTransactionManagementConfiguration

    首相,它是一个配置类,并且继承于一个配置类(AbstractTransactionManagementConfiguration),后者是一个抽象类,抽象类肯定是提供一些和事务相关的一些基础的配置,比如@TransactionalEventListener注解的操作的工厂类。通过TransactionManagementConfigurer来配置

    TransactionManager

    ProxyTransactionManagementConfiguration里面主要配置了三个bean

    • BeanFactoryTransactionAttributeSourceAdvisor

      它是一个PointcutAdvisor,主要是用来获取JointPoint(切入点就是TransactionAttributeSourcePointcut,在它里面需要TransactionAttributeSource对象,用作JointPoint判断的时候的比对),之前在代理对象创建的时候说过这个,对于一个Advice和Advisor的区别。

    • TransactionAttributeSource

      能拿到事务的所有的信息(通过它可以获取到事务的元信息,也就是在调用@Transaction注解标志的方法时候,拿到注解上面的元信息),此外提供了比对的功能,即就是这个方法能不能匹配的到,jointPoint是代理对象里面必须的,真正的逻辑是在它的isCandidateClass方法里面.此外,Spring除了自己的@Transactional注解之外,还提供了对别的注解的支持,就是通过TransactionAnnotationParser接口来做比对,不同的注解对应不同的实现类,如下图:

在这里插入图片描述

  • TransactionInterceptor

    事务的拦截器,在执行目标方法的时候的Interceptor,那些和事务相关的逻辑的地方就是这,这个很重要

    在创建它的时候需要TransactionAttributeSource对象。

在创建代理对象的时候,@Transaction注解是怎么做匹配的?

换句话来说,Spring是怎么知道标注了@Transaction注解的方法需要创建代理对象。

前面说了,会导入AutoProxyRegistrar,它会导入AbstractAutoProxyCreator来创建代理对象,在创建代理对象的时候会拿到所有的Advisor,做匹配。相关方法在 AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply里面。

在这里插入图片描述

前面说了,@EnableTransactionManager会导入BeanFactoryTransactionAttributeSourceAdvisor 。他是一个Advisor,在匹配的时候会拿到他的ClassFilter做匹配,在BeanFactoryTransactionAttributeSourceAdvisor里面,classFilter如下所示

一般来说,在做匹配的过程中,先匹配ClassFilter,在做MethodMatcher的匹配。

在这里插入图片描述

最终会调用到TransactionAttributeSource,在创建BeanFactoryTransactionAttributeSourceAdvisor的时候就已经设置进来了,在TransactionAttributeSource里面,调用TransactionAnnotationParser来做判断解析操作,返回为true表示,匹配,就可以创建代理对象了,给代理对象应用的MethodIntercept是TransactionInterceptor

事务的传播级别相关说明

传播级别有哪几类?

要回答这个问题,直接看@Transaction注解propagation属性对应的枚举值。总共有下面的几种类型

  1. REQUIRED,required,如果没有事务,就创建一个新的事务,如果有,就支持(同用已经存在的),

  2. SUPPORTS,supports,没有事务,就以没有事务的方式来操作,如果有,就支持(同用已经存在的)。

  3. MANDATORY,mandatory,必须的有事务,没有事务就报错,如果有,就支持(同用已经存在的)。

  4. REQUIRES_NEW,require_new,创建一个新的事务,如果当前有事务,就挂起当前事务。(需要注意的是,这个操作并不是完全适用于所有的TransactionManager,需要使用JtaTransactionManager并且需要的事务得是javax.transaction.TransactionManager)

  5. NOT_SUPPORTED,not_supported,不支持事务,如果当前有事务,就挂起当前事务,以非事务的方式来运行,(需要注意的是,这个操作并不是完全适用于所有的TransactionManager,需要使用JtaTransactionManager并且需要的事务得是javax.transaction.TransactionManager)

  6. NEVER,never,不支持事务,按照非事务的方式运行,如果有当前有存在事务就报错。

  7. NESTED,nested,嵌套事务,如果当前有事务存在,执行嵌套事务,不存在就会创建一个新的事务执行就像(REQUIRED),嵌套事务的实际创建只能在特定的事务管理器上工作,这只适用于JDBC的DataSourceTransactionManager。

    内层的事务的回滚不会影响外层的事务,外层事务的回滚会影响内层事务。

默认的事务的传播级别是 REQUIRED

@Transaction注解属性说明
package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;
import org.springframework.transaction.TransactionDefinition;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
  // 指定事务管理者
	@AliasFor("transactionManager")
	String value() default "";

	@AliasFor("value")
	String transactionManager() default "";
	// 传播级别
	Propagation propagation() default Propagation.REQUIRED;
  // 事务的隔离级别,这可是真正的设置给数据库的
	Isolation isolation() default Isolation.DEFAULT;
  // 事务的超时时间
	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
 // 是否是只读事务
	boolean readOnly() default false;
 // 回滚的异常
	Class<? extends Throwable>[] rollbackFor() default {};
 // 回滚的异常的名字,可以是全限定类名,也可以是一个简答的名字,比如Exception会几乎会匹配所有的异常。
	String[] rollbackForClassName() default {};
	// 不要回滚
	Class<? extends Throwable>[] noRollbackFor() default {};

	String[] noRollbackForClassName() default {};

}

rollbackFor属性默认的回滚是RuntimeException,和Error。检查性的异常默认不会处理的,可以看DefaultTransactionAttribute#rollbackOn(Throwable)方法,属性值是一个数组,这就有一点点的不方便,并且不会匹配子类,要想匹配很多的话,建议用 rollbackForClassName

其实 rollbackFor和rollbackForClassName本质上都是一样的,都是RollbackRuleAttribute实体类,他里面就一个exceptionName属性,在构建的时候如果传递进来的是class对象,就获取全限定类型,如果是String(对应的就是rollbackForClassName)就直接赋值。

在判断是否要回滚的时候是获取异常的全限定类名,看里面是否有 exceptionName。所以当rollbackFor={Exception.class}和rollbackForClassName={“Exception”}是一样的。

private int getDepth(Class<?> exceptionClass, int depth) {
		if (exceptionClass.getName().contains(this.exceptionName)) {
			// Found it!
			return depth;
		}
		// If we've gone as far as we can go and haven't found it...
		if (exceptionClass == Throwable.class) {
			return -1;
		}
		return getDepth(exceptionClass.getSuperclass(), depth + 1);
	}

话说我也没有想到,居然是这样实现的。

代码实现分析

要知道的一点是,事务的传播级别是Spring的东西,和Mysql是没有关系的,Mysql只有隔离级别。还得知道,在JDBC中事务是在一个Connection对象开启的,上面所说的共用事务,挂起事务,这些都是对Connection对象的操作,比如说,共用事务,比如说,方法a标注了@Transaction注解,在a方法里面会有对数据库的操作,这整个过程中都用的是同一个Connection对象,得在a方法之前把Connection对象创建好,关闭它的自动提交的功能,在方法a里面就可以在某个地方来获取connection,这里说的就是ThreadLocal。这里肯定是用得到的。再比如说挂起的操作,得把之前在ThreadLocal中的Connection放在一个地方保存起来,重新创建一个新的事务,放在ThreadLocal中,执行完了之后,恢复之前挂起的Connection。这点要清楚。

事务是通过JDBC来操作的,如下面的代码


Connection conn = DataSource.getConnection();

// 设置自动提交 false
conn..setAutoCommit(false);

try {
    // 业务操作
    doSomething();
} catch (Exception e) {
    // 回滚事务
    conn.rollback();
}
// 提交事务
conn.commit();

Spring实现事务多了一些问题需要解决,

比如在业务代码里面肯定是要用Connection对象的,如何保证设置了事务的操作的Connection和业务代码里面的是一个对象。在挂起之后,之前的事务资源如果表示,恢复的时候要如何表示等等问题,先说前一个问题,

  • 业务代码中的和设置事务的Connection如何保证一致?

    通过ThreadLocal能做到。只要业务代码中用到获取JDBC的操作和设置事务的在一个地方就行。

  • 挂起资源如果表示,恢复的时候怎么恢复?

在开启新的事务之前,要将之前ThreadLocal中的Connection和当前的事务资源封装为对象,将它当前事务对象一个属性,在恢复的时候就可以拿过来,恢复成原来的样子。

从TransactionInterceptor开始

断点打到TransactionInterceptor#invoke方法上,按照上面的例子,先跑起来就会看到下面的代码(TransactionAspectSupport)

这就是事务的核心代码,可以慢慢的看。

TransactionAttributeSource是用来检索事务的元信息的。比如事务的传播级别,隔离级别等等。

只要干了下面的几个事情:

  1. 通过TransactionAttributeSource来获取当前方法或者类上的注解的元信息,封装为TransactionAttribute

  2. 找到PlatformTransactionManager

  3. 创建TransactionInfo对象,在这个对象里面包含了上一个事务的信息(如果需要就是挂起的资源)当前事务的状态(是否有savePoint,是否是isRollbackOnly),事务的元信息,事务管理者。当前的连接点。

在这里插入图片描述

  1. 调用目标方法

  2. 处理异常,如果需要回滚就回滚,有savePoint就回滚到SavePoint,没有匹配到就提交。

  3. 恢复上一个事务的信息。

  4. 提交当前的事务。

	@Nullable
	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
			final InvocationCallback invocation) throws Throwable {

	  
		TransactionAttributeSource tas = getTransactionAttributeSource();
        // 获取事务的定义信息
		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        // 拿到TransactionManager,如果没有指定,就从Application中找
		final TransactionManager tm = determineTransactionManager(txAttr);

      ......
        // 转化为PlatformTransactionManager
		PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
        // 拿到事务应用方法的签名,也就是当前方法的前面
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
		 // 这里是重点,会拿到事务的信息,TransactionInfo,同时也会处理传播级别,开启事务的操作,还有封装当前的事务状态作为它的一个属性,便于之后恢复
			TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

			Object retVal;
			try {
			 // 调用目标方法
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// 出错,处理异常,需要回滚就回滚,又savePoint就直接到SavePoint
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
                // 恢复之前的事务状态,
				cleanupTransactionInfo(txInfo);
			}

			if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
				// Set rollback-only in case of Vavr failure matching our rollback rules...
				TransactionStatus status = txInfo.getTransactionStatus();
				if (status != null && txAttr != null) {
					retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
				}
			}
            // 提交事务
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

	 ......
	}

下面就按照上面的几个步骤对照源码分析分析

创建TransactionInfo

对照上面TransactionInfo的属性发现,前几个基本一开始就确定好了,唯独TransactionStatusTransactionInfo不能确定。下面的部分得分为这两部分来看

创建TransactionStatus

它是通过AbstractPlatformTransactionManager#getTransaction(TransactionDefinition)来创建的,这里的代码比较复杂。代码如下:

主要的逻辑如下:

  1. 获取事务对象,判断当前是否有事务(其实就是是否有Connection),如果有就走handleExistingTransaction,否则就继续往下走
  2. 通过不同的传播行为走不同的逻辑,比如PROPAGATION_MANDATORY就直接报错。
  3. 如果需要创建一个新的事务,在创建新事务之前,先将之前的事务挂起,但是这里走到这里说明之前就没有事务,所以挂起的资源就是一个null。
  4. 如果传播行为是Never,就只是创建了一个DefaultTransactionStatus,封装了事务的元信息,是否是新事务,日志级别,挂起的资源。之后对它设置了一些东西,这个后面会说
	public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {

	   // 如果没有事务的元信息的话,就用默认的
		TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
       	// 获取事务对象,这是留给子类拓展的,
		Object transaction = doGetTransaction();
		boolean debugEnabled = logger.isDebugEnabled();
       // 是否存在事务,这也是留给子类拓展的
		if (isExistingTransaction(transaction)) {
		 // 通过传播行为来确定行为
			return handleExistingTransaction(def, transaction, debugEnabled);
		}
	 // 从这里往下,就说明之前没有事务,通过不同的传播行为来确定不同的对象,
		 
		if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
			throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
		}

		// 传播行为是强制,但是当前却没有事务,直接报错
		if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
			throw new IllegalTransactionStateException(
					"No existing transaction found for transaction marked with propagation 'mandatory'");
		}
        //下面的这三种都是需要创建挂起事务,创建新事务的 
		else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
				def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
				def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            // 挂起事务资源,因为当前没有事务,这里直接一个null,所以SuspendedResourcesHolder也就是一个null
			SuspendedResourcesHolder suspendedResources = suspend(null);
			if (debugEnabled) {
				logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
			}
			try {
                // 开启一个新的事务,
				return startTransaction(def, transaction, debugEnabled, suspendedResources);
			}
			catch (RuntimeException | Error ex) {
				resume(null, suspendedResources);
				throw ex;
			}
		}
		else {
			// Create "empty" transaction: no actual transaction, but potentially synchronization.
			if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
				logger.warn("Custom isolation level specified but no actual transaction initiated; " +
						"isolation level will effectively be ignored: " + def);
			}
            //走到这里的,只有一个了,就是Never,表示不需要事务,这里不会开启新事务,支持创建了一个DefaultTransactionStatus,操作了一下。
			boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
			return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
		}
	}

TransactionManager接口有不同的实现类,这里主要说的是DataSourceTransactionManager

  1. 获取事务(doGetTransaction())和判断是否有事务(isExistingTransaction(Object))

    创建DataSourceTransactionObject对象,它里面主要包含了ConnectionHolder对象,ConnectionHolder包含了Connection和一些标志位,比如事务是否活动。

    获取当前的DataSoruce,从ThreadLocal中获取一个Map,从Map中检索是否有ConnectionHolder对象(具体代码在TransactionSynchronizationManager#getResource(Object)),有的话,就说明之前有事务,没有就没有。

    在创建好的DataSourceTransactionObject中获取ConnectionHolder对象,ConnectionHolder不是null并且事务是活动的,说明当前有事务存在。

  2. 开启一个事务(AbstractPlatformTransactionManager#startTransaction(...)

    判断是否要创建新的事务,是否要创建新的同步资源(其实就是是否要初始化TransactionSynchronization所在ThreadLocal),下面会讲TransactionSynchronization 创建DefaultTransactionStatus,属性如下:

在这里插入图片描述

下面所说的代码对应的位置在DataSourceTransactionManager#doBegin(Object,TransactionDefinition)

利用DataSource获取Connection,设置到ConnectionHolder中去,在设置一波标志位,比如,ConnectionHolder#synchronizedWithTransaction属性为true:表示是否支持TransactionSynchronization。),设置ConnectionHolder#transactionActive属性为true:表示当前事务激活了,设置ConnectionHolder#newConnectionHolder为true:表示它是一个新的连接。

下面在设置一些属性给DataSourceTransactionObject(之前说了,它就是一个事务对象),比如隔离级别,是否只读(这是直接设置给Connection的),关闭自动提交(这是直接设置给Connection的),设置超时时间给ConnectionHolder。

将设置好的ConnectionHolder放在ThreadLocal中,上一步是这么取得,这里就得怎么放。还是拿到Map,DataSource作为key,ConnectionHolder做为value存放在里面这里得注意,这个ThreadLocal可是真正的存放了Connection的。业务代码里面操作数据库就获取Connection的时候直接在这里面拿就能实现前面说的,事务的Connection和业务代码的Connection是一个Connection,还得注意一点,connectionHolder是和DataSource相关的。对应的代码在(TransactionSynchronizationManager.bindResource(Object,Object)

到这,Connection已经创建好了,事务对象里面的ConenctionHolder也已经设置好了Conenction,并且也放在ThreadLocal里面去了。DefaultTransactionStatus对象也创建好了。下一步就需要在做一些额外的操作了(主要是对新创建的DefaultTransactionStatus,如果支持TransactionSynchronization,就在TransactionSynchronizationManager中的ThreadLocal中设置一些属性。)

如果当前事务是一个可以同步资源的,就设置当前活动的事务,当前的隔离级别,当前事务是否只读,当前事务的名字,并且初始化TransactionSynchronizationManager#synchronizations属性,在里面塞一个空集合。(对应的代码在AbstractPlatformTransactionManager#prepareSynchronization

到这里,TransactionStatus已经创建好了


这里就一次性将挂起也说了吧

  1. 在构建TransactionStatus的时候需要上一个事务挂起的资源。挂起的操作在(AbstractPlatformTransactionManager#suspend

    挂起就是保存之前的状态,首先,先要清理这个事务绑定的资源,所以,得先拿到TransactionSynchronizationManager里面的TransactionSynchronization集合,调用他们的suspend()方法。然后从ThreadLocal中移除。也得把之前保存的ConnectionHolder拿出来,将一些同步的资源拿出来,也就是在开启事务的时候设置的那些,比如当前事务的名字,当前事务的隔离状态等等,并且将ThreadLocal中变为null,将这些信息全部封装为SuspendedResourcesHolder对象返回,这就在创建TransactionStatus的时候,就可以用了。

创建TransactionInfo(这里说的是上一个事务。)

具体的代码在 TransactionAspectSupport#bindToThread,

在这里插入图片描述

直接从ThreadLocal中获取原来的,并且作为自己的属性,并且将自己绑定上去。

上面说的是,如果一上来就不存在事务的逻辑,如果存在事务会怎么弄对应的代码在 AbstractPlatformTransactionManager#handleExistingTransaction(TransactionDefinition,Object,boolean)

如果一开始就存在事务会怎么做?

一上来还是先检查传播属性,不同的传播级别有不同的行为。主要调用的方法还是上面介绍过的方法。

PROPAGATION_NEVER,直接报错。

PROPAGATION_NOT_SUPPORTED:挂起当前的事务资源,创建DefaultTransactionStatus,在往ThreadLocal中塞东西。

PROPAGATION_REQUIRES_NEW:挂起当前的资源,开启一个新的事务。

PROPAGATION_NESTED:如果支持就不会开启新的事务,但是会创建DefaultTransactionStatus,关闭所有的同步标志位,通过SavepointManager创建Savepoint。如果不支持,就会开启一个新的事务。

回滚操作

下面对应的代码在TransactionAspectSupport#completeTransactionAfterThrowing(TransactionInfo,Throwable)

回滚操作在JDBC中就是Connection#Rollback(),对于回滚操作,首先要判断当前的异常是否要回滚(TransactionAttribute#rollbackOn()),如果不需要回滚直接提交。

事务是可以和资源绑定在一起的,其实就是在执行事务的时候有提供了TransactionSynchronization接口,在事务的各个阶段做回调,他们就是一个整体。(AbstractPlatformTransactionManager#processRollback)。

如果DefaultTransactionStatus#newSynchronization为true表示可以调用。先调用TransactionSynchronization#beforeCompletion(),看是否有savePoint,通过SavepointManager#rollbackToSavepoint(Object)回滚到savePoint,在通过SavepointManager#releaseSavepoint(Object)释放savePoint,最后把它的引用变为null。

如果有没有,当前是一个新的事务,就调用从利用dataSource从Map中获取到ConnectionHolder,拿到Connection,调用它的rollback方法。

如果当前的事务不是一个新的事务,并且还有事务对象,那它可能就是一个长事务,设置ConnectionHolder#rollbackOnly为true。

不管异常还是正常结束,调用TransactionSynchronization#afterCompletion(int),将当前的状态传递获取,异常为TransactionSynchronization.STATUS_UNKNOWN,正常的为TransactionSynchronization.STATUS_ROLLED_BACK,最后设置DefaultTransactionStatus#completed为true:表示事务已经完成(提交或者已经回滚了)。将之前的同步调用的TransactionSynchronization清楚掉,如果是新事务需要将拿到ConnectionHolder,拿到Connection,将之前设置的Connection的属性都设置回来。最后关闭掉Connection,如果有挂起的资源,就需要将他们重新恢复

恢复操作

恢复就是将之前保存的资源重新设置了,比如,重新绑定ConnectionHolder,重新设置TransactionSynchronization集合。当前事务的名字等等,调用TransactionSynchronization#resume()

补充说明

TransactionSynchronization的说明

它是为了事务执行的时候同步回调的。里面的方法对应着事务执行的各个阶段。可以通过这个接口让普通的方法也具有事务性质,比如你需要在一个事务的方法里面做一些操作,比如请求外部接口,等等,可以通过实现这个接口,并且将它添加到TransactionSynchronizationManager里面就好了。需要注意:他是和当前事务直接挂钩的,它也是作为挂起资源的一部分,代码在(SuspendedResourcesHolder#suspendedSynchronizations),最后会将SuspendedResourcesHolder封装在DefaultTransactionStatus#suspendedResources的。

需要知道,在调用这些方法的时候,Connection还没有关闭的。还是可以做一些操作的。也可以在这些方法里面做一些额外的操作,比如事务提交之后,要发送邮件,或者往Mq里面发送消息等等。

public interface TransactionSynchronization extends Flushable {

	/** Completion status in case of proper commit. */
	int STATUS_COMMITTED = 0;

	/** Completion status in case of proper rollback. */
	int STATUS_ROLLED_BACK = 1;

	/** Completion status in case of heuristic mixed completion or system errors. */
	int STATUS_UNKNOWN = 2;

    // 挂起
	default void suspend() {
	}

   // 恢复
	default void resume() {
	}


	 // 刷新数据源的session
	default void flush() {
	}

   // 在commit和beforeCompletion之前调用,不会真正的提交,还是有机会改变的。
	default void beforeCommit(boolean readOnly) {
	}
   // 在commit和rollback之前调用,可以执行一些资源清理的工作在事务完成之前,
    // beforeCompletion在beforeCommit之后,事务Commit之前
	default void beforeCompletion() {
	}
   // 事务提交之后的回调,它在afterCompletion之前
	default void afterCommit() {
	}
  // 他是最后的的操作
	default void afterCompletion(int status) {
	}

}

TransactionSynchronizationManager

上面黄色背景的说的就是这个,他里面包含了当前事务的信息。

上面说的那些ThreadLocal得放在一个地方吧。这个地方就是(TransactionSynchronizationManager),它里面放了一堆ThreadLocal。如下所示:

   // 这就是上面说的大Map,ThreadLocal中存放Map。Map的key是dataSource,value是ConnectionHolder 
   private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");   
   //这里面存放的是TransactionSynchronization的集合
	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");
  // 当前事务的名字
	private static final ThreadLocal<String> currentTransactionName =
			new NamedThreadLocal<>("Current transaction name");
 // 当前事务是否是 read-only
	private static final ThreadLocal<Boolean> currentTransactionReadOnly =
			new NamedThreadLocal<>("Current transaction read-only status");
   // 当前事务的隔离级别
	private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
			new NamedThreadLocal<>("Current transaction isolation level");
   // 当前事务是否活动
	private static final ThreadLocal<Boolean> actualTransactionActive =
			new NamedThreadLocal<>("Actual transaction active");

要注意,这里面存放的都是当前事务的信息,如果要开启事务,会将它里面的属性值取出来,一起组成 SuspendedResourcesHolder ,恢复的时候又会恢复回去

在操作TransactionSynchronization的时候,得先判断是否有,代码如下:

	public static boolean isSynchronizationActive() {
		return (synchronizations.get() != null);
	}

rollbackOnly说明

rollbackOnly是一个boolean值,用它来控制是否要回滚,尤其是在长事务中,至关重要。理论上来说,

他是事务提交操作(AbstractPlatformTransactionManager#commit(TransactionStatus))里面的一个前置的增强的操作,在没有异常的情况下,也可以回滚事务。

先看Spring事务中有几个地方用到了这个。

  1. DefaultTransactionStatus#rollbackOnly属性

    这就要说到一个应用的范围了,DefaultTransactionStatus准确的说,应该是当前事务的状态,也就是说表示的当前事务的状态,只有有@Transactional注解标注的方法, 当他生效的时候每次都会创建一个新的DefaultTransactionStatus对象,他的应用范围最小。但是得先判断

  2. AbstractPlatformTransactionManager#shouldCommitOnGlobalRollbackOnly()方法

    他是一个全局的,是属于当前的事务管理器的

  3. SmartTransactionObject#isRollbackOnly()

    他是真正起作用的事务,他里面包含了Connection,谁有Connection的引用谁才最厉害。虽然 DataSourceTransactionObject最后也是作为DefaultTransactionStatus的一个属性,不好区分,但是可以增加标志位(newTransaction来表示有没有创建新的事务)

  4. AbstractPlatformTransactionManager#failEarlyOnGlobalRollbackOnly属性

    他表示不需要抛出异常。

这种机制用在长事务里面就很方便,如果在A->B的调用链路里面,A在调用B的时候处理了异常,如果他俩的传播级别都是REQUIRED,A就不会捕获到异常,如果没有这个机制,A就会将事务提交,从而导致B里面的错误数据也提交。这就是问题。有了这个机制,在B报错之后,(TransactionInterceptor回捕获到异常,设置SmartTransactionObject的rollbackOnly),A执行完了之后,在提交事务的时候,发现标志位为true了,就得回滚,从而避免了产生上面说的问题。


关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值