从源码分析为什么spring事务不能跨线程

一、准备工作,环境搭建

1、POM文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.szrym</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-jdbc</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.48</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2、application.properties文件

spring.datasource.url=jdbc:mysql://192.168.3.36:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

3、项目启动对应的各个类

  • 启动类

    package cn.szrym.springjdbc;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    @SpringBootApplication
    @EnableTransactionManagement
    @MapperScan("cn.szrym.springjdbc.dao")
    public class SpringJdbcApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringJdbcApplication.class, args);
        }
    }
    
    
  • 控制器

    package cn.szrym.springjdbc.controller;
    
    import cn.szrym.springjdbc.entity.Student;
    import cn.szrym.springjdbc.service.IStudentService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    @RestController
    @RequestMapping("/student")
    public class StudentController {
        @Autowired
        private IStudentService studentService;
        @PostMapping(value = "/{version}")
        public void insert(@PathVariable("version") String version,@RequestBody Student student){
            studentService.insert(student);
        }
    }
    
  • 服务类

    package cn.szrym.springjdbc.service;
    
    import cn.szrym.springjdbc.dao.IStudentDao;
    import cn.szrym.springjdbc.entity.Student;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import javax.annotation.Resource;
    
    @Service
    public class  StudentServiceImpl implements IStudentService {
        @Resource
        private IStudentDao studentDao;
    
        @Autowired
        private ApplicationContext context;
    
        @Transactional
        public void insert(Student student){
         insert0(student);
    
        }
    
        /**
         * 异步调用插入的方法
         * @param student
         */
        @Transactional
        public void insert1(Student student){
            IStudentService self = context.getBean(IStudentService.class);
            new Thread(()->{
                     self.insert0(student);
                }).start();
        }
        @Transactional
        public void insert0(Student student){
            studentDao.insertStudent(student);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    
  • 数据访问类

package cn.szrym.springjdbc.dao;

import cn.szrym.springjdbc.entity.Student;
import org.apache.ibatis.annotations.Insert;

public interface IStudentDao {
    @Insert("INSERT INTO student (NAME,age) VALUE(#{name},#{age})")
    void insertStudent(Student student);
}

二、环境测试

1、启动对应的数据库

在这里插入图片描述

2、启动项目,成功启动后的输出如下

D:\softInstall\jdk\bin\java.exe -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:63613,suspend=y,server=n -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:C:\Users\zhshl\AppData\Local\JetBrains\IntelliJIdea2020.1\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "D:\softInstall\jdk\jre\lib\charsets.jar;D:\softInstall\jdk\jre\lib\deploy.jar;D:\softInstall\jdk\jre\lib\ext\access-bridge-64.jar;D:\softInstall\jdk\jre\lib\ext\cldrdata.jar;D:\softInstall\jdk\jre\lib\ext\dnsns.jar;D:\softInstall\jdk\jre\lib\ext\jaccess.jar;D:\softInstall\jdk\jre\lib\ext\jfxrt.jar;D:\softInstall\jdk\jre\lib\ext\localedata.jar;D:\softInstall\jdk\jre\lib\ext\nashorn.jar;D:\softInstall\jdk\jre\lib\ext\sunec.jar;D:\softInstall\jdk\jre\lib\ext\sunjce_provider.jar;D:\softInstall\jdk\jre\lib\ext\sunmscapi.jar;D:\softInstall\jdk\jre\lib\ext\sunpkcs11.jar;D:\softInstall\jdk\jre\lib\ext\zipfs.jar;D:\softInstall\jdk\jre\lib\javaws.jar;D:\softInstall\jdk\jre\lib\jce.jar;D:\softInstall\jdk\jre\lib\jfr.jar;D:\softInstall\jdk\jre\lib\jfxswt.jar;D:\softInstall\jdk\jre\lib\jsse.jar;D:\softInstall\jdk\jre\lib\management-agent.jar;D:\softInstall\jdk\jre\lib\plugin.jar;D:\softInstall\jdk\jre\lib\resources.jar;D:\softInstall\jdk\jre\lib\rt.jar;D:\learn\java\spring-tx\spring-jdbc\target\classes;E:\data\repository\org\mybatis\spring\boot\mybatis-spring-boot-starter\2.1.2\mybatis-spring-boot-starter-2.1.2.jar;E:\data\repository\org\springframework\boot\spring-boot-starter\2.2.6.RELEASE\spring-boot-starter-2.2.6.RELEASE.jar;E:\data\repository\org\springframework\boot\spring-boot\2.2.6.RELEASE\spring-boot-2.2.6.RELEASE.jar;E:\data\repository\org\springframework\boot\spring-boot-autoconfigure\2.2.6.RELEASE\spring-boot-autoconfigure-2.2.6.RELEASE.jar;E:\data\repository\org\springframework\boot\spring-boot-starter-logging\2.2.6.RELEASE\spring-boot-starter-logging-2.2.6.RELEASE.jar;E:\data\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;E:\data\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;E:\data\repository\org\apache\logging\log4j\log4j-to-slf4j\2.12.1\log4j-to-slf4j-2.12.1.jar;E:\data\repository\org\apache\logging\log4j\log4j-api\2.12.1\log4j-api-2.12.1.jar;E:\data\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;E:\data\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\data\repository\org\yaml\snakeyaml\1.25\snakeyaml-1.25.jar;E:\data\repository\org\springframework\boot\spring-boot-starter-jdbc\2.2.6.RELEASE\spring-boot-starter-jdbc-2.2.6.RELEASE.jar;E:\data\repository\com\zaxxer\HikariCP\3.4.2\HikariCP-3.4.2.jar;E:\data\repository\org\springframework\spring-jdbc\5.2.5.RELEASE\spring-jdbc-5.2.5.RELEASE.jar;E:\data\repository\org\springframework\spring-tx\5.2.5.RELEASE\spring-tx-5.2.5.RELEASE.jar;E:\data\repository\org\mybatis\spring\boot\mybatis-spring-boot-autoconfigure\2.1.2\mybatis-spring-boot-autoconfigure-2.1.2.jar;E:\data\repository\org\mybatis\mybatis\3.5.4\mybatis-3.5.4.jar;E:\data\repository\org\mybatis\mybatis-spring\2.0.4\mybatis-spring-2.0.4.jar;E:\data\repository\org\springframework\boot\spring-boot-starter-aop\2.2.6.RELEASE\spring-boot-starter-aop-2.2.6.RELEASE.jar;E:\data\repository\org\springframework\spring-aop\5.2.5.RELEASE\spring-aop-5.2.5.RELEASE.jar;E:\data\repository\org\springframework\spring-beans\5.2.5.RELEASE\spring-beans-5.2.5.RELEASE.jar;E:\data\repository\org\aspectj\aspectjweaver\1.9.5\aspectjweaver-1.9.5.jar;E:\data\repository\org\springframework\boot\spring-boot-starter-web\2.2.6.RELEASE\spring-boot-starter-web-2.2.6.RELEASE.jar;E:\data\repository\org\springframework\boot\spring-boot-starter-json\2.2.6.RELEASE\spring-boot-starter-json-2.2.6.RELEASE.jar;E:\data\repository\com\fasterxml\jackson\core\jackson-databind\2.10.3\jackson-databind-2.10.3.jar;E:\data\repository\com\fasterxml\jackson\core\jackson-annotations\2.10.3\jackson-annotations-2.10.3.jar;E:\data\repository\com\fasterxml\jackson\core\jackson-core\2.10.3\jackson-core-2.10.3.jar;E:\data\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.10.3\jackson-datatype-jdk8-2.10.3.jar;E:\data\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.10.3\jackson-datatype-jsr310-2.10.3.jar;E:\data\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.10.3\jackson-module-parameter-names-2.10.3.jar;E:\data\repository\org\springframework\boot\spring-boot-starter-tomcat\2.2.6.RELEASE\spring-boot-starter-tomcat-2.2.6.RELEASE.jar;E:\data\repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.33\tomcat-embed-core-9.0.33.jar;E:\data\repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.33\tomcat-embed-el-9.0.33.jar;E:\data\repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.33\tomcat-embed-websocket-9.0.33.jar;E:\data\repository\org\springframework\boot\spring-boot-starter-validation\2.2.6.RELEASE\spring-boot-starter-validation-2.2.6.RELEASE.jar;E:\data\repository\jakarta\validation\jakarta.validation-api\2.0.2\jakarta.validation-api-2.0.2.jar;E:\data\repository\org\hibernate\validator\hibernate-validator\6.0.18.Final\hibernate-validator-6.0.18.Final.jar;E:\data\repository\org\jboss\logging\jboss-logging\3.4.1.Final\jboss-logging-3.4.1.Final.jar;E:\data\repository\com\fasterxml\classmate\1.5.1\classmate-1.5.1.jar;E:\data\repository\org\springframework\spring-web\5.2.5.RELEASE\spring-web-5.2.5.RELEASE.jar;E:\data\repository\org\springframework\spring-webmvc\5.2.5.RELEASE\spring-webmvc-5.2.5.RELEASE.jar;E:\data\repository\org\springframework\spring-context\5.2.5.RELEASE\spring-context-5.2.5.RELEASE.jar;E:\data\repository\org\springframework\spring-expression\5.2.5.RELEASE\spring-expression-5.2.5.RELEASE.jar;E:\data\repository\mysql\mysql-connector-java\5.1.48\mysql-connector-java-5.1.48.jar;E:\data\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;E:\data\repository\org\springframework\spring-core\5.2.5.RELEASE\spring-core-5.2.5.RELEASE.jar;E:\data\repository\org\springframework\spring-jcl\5.2.5.RELEASE\spring-jcl-5.2.5.RELEASE.jar;D:\softInstall\IDEA\IntelliJ IDEA 2020.1.1\lib\idea_rt.jar" cn.szrym.springjdbc.SpringJdbcApplication
Connected to the target VM, address: '127.0.0.1:63613', transport: 'socket'

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.6.RELEASE)

2020-05-07 07:45:27.407  INFO 20588 --- [           main] c.s.springjdbc.SpringJdbcApplication     : Starting SpringJdbcApplication on DESKTOP-IPCLV9O with PID 20588 (D:\learn\java\spring-tx\spring-jdbc\target\classes started by zhshl in D:\learn\java\spring-tx)
2020-05-07 07:45:27.410  INFO 20588 --- [           main] c.s.springjdbc.SpringJdbcApplication     : No active profile set, falling back to default profiles: default
2020-05-07 07:45:28.245  INFO 20588 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-05-07 07:45:28.253  INFO 20588 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-05-07 07:45:28.253  INFO 20588 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.33]
2020-05-07 07:45:28.310  INFO 20588 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-05-07 07:45:28.310  INFO 20588 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 857 ms
2020-05-07 07:45:28.560  INFO 20588 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-05-07 07:45:28.703  INFO 20588 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-07 07:45:28.705  INFO 20588 --- [           main] c.s.springjdbc.SpringJdbcApplication     : Started SpringJdbcApplication in 1.584 seconds (JVM running for 2.955)

3、发送请求到服务器

此时能够正常的存储数据,查询数据库中的结果如下:
在这里插入图片描述

4、修改StudentServiceImpl类中的方法

添加一个会产生异常的语句

   @Transactional
    public void insert(Student student){
     insert0(student);
     int i = 1/0;

    }

5、重启应用再次进行测试

控制台输出了如下错误信息:

java.lang.ArithmeticException: / by zero
	at cn.szrym.springjdbc.service.StudentServiceImpl.insert(StudentServiceImpl.java:24) ~[classes/:na]

查询数据库发现数据没有被正确的添加。至此,已经证明基于spring的事务环境搭建成功。

三、spring 事务执行源码分析

我们都知道,spring的事务原理是基于AOP来进行实现的,当我们开启了事务,如果spring中的Bean(如上文的StudentServiceImpl)中有@Transactional注解注释类或者方法,那么spring 就会为该类生成一个动态代理对象。那么这里面具体是怎么实现的呢?

spring 事务处理的调用的开始类为:org.springframework.transaction.interceptor.TransactionInterceptor。

其整个调用的时序图如下:

在这里插入图片描述

1、invoke()方法解读

  //org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
@Override
    @Nullable
    public Object invoke(MethodInvocation invocation) throws Throwable {
        //计算出目标方法
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

        // 调用TransactionAspectSupport的invokeWithinTransaction...
        return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
    }

从源码可以看出是直接调用 TransactionAspectSupport 的 invokeWithinTransaction方法进行事务的处理

2、invokeWithinTransaction()方法解读

核心代码如下:

  //org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
@Nullable
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
                                             final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {

        // 获取事务的属性  可能为Null ,为null的时候说明是一个非事务的 方法
        TransactionAttributeSource tas = getTransactionAttributeSource();
        final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
        //确定使用哪种事务管理器
        final TransactionManager tm = determineTransactionManager(txAttr);

        //如果是反应式事务
        if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
            TransactionAspectSupport.ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
                if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && TransactionAspectSupport.KotlinDelegate.isSuspend(method)) {
                    throw new TransactionUsageException(
                            "Unsupported annotated transaction on suspending function detected: " + method +
                                    ". Use TransactionalOperator.transactional extensions instead.");
                }
                ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
                if (adapter == null) {
                    throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
                            method.getReturnType());
                }
                return new TransactionAspectSupport.ReactiveTransactionSupport(adapter);
            });
            return txSupport.invokeWithinTransaction(
                    method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
        }
        //将事务管理器转换为对应平台的事务管理器
        PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
        final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

        if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
            // 获取到标准事务的相关信息   该方法是后续分析的重点  里面处理了创建事务,绑定事务的属性
            TransactionAspectSupport.TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

            Object retVal;
            try {

                //调用目标方法
                retVal = invocation.proceedWithInvocation();
            }
            catch (Throwable ex) {
                //   如果目标方法 发生异常  ,那么就行异常方面的处理
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }
            finally {
                //清理线程上绑定的相关资源信息
                cleanupTransactionInfo(txInfo);
            }

            if (vavrPresent && TransactionAspectSupport.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 = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
                }
            }
            //对事务进行提交
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }
    }
  • 1、通过事务的属性来获取对应的事务管理器
  • 2、将事务管理器转换为平台对应的事务管理器
  • 3、通过事务平台事务管理器来开启的事务,并从数据源中获取到一个数据库的连接绑定到当前线程
  • 4、调用目标方法,目标方法中对应的进行的数据操作也使用这同一个数据库的连接
  • 5、如果发异常,就对当前事务进行回滚。(当然也可能不回滚)
  • 6、对事务进行提交操作,同时会解除当前线程上绑定的connection

3、createTransactionIfNecessary()方法解读

	//org.springframework.transaction.interceptor.TransactionAspectSupport#createTransactionIfNecessary
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
			@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

		// If no name specified, apply method identification as transaction name.
		if (txAttr != null && txAttr.getName() == null) {
			txAttr = new DelegatingTransactionAttribute(txAttr) {
				@Override
				public String getName() {
					return joinpointIdentification;
				}
			};
		}

		TransactionStatus status = null;
		if (txAttr != null) {
			if (tm != null) {
                // 获取事务的核心方法
				status = tm.getTransaction(txAttr);
			}
			else {
				if (logger.isDebugEnabled()) {
					logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
							"] because no transaction manager has been configured");
				}
			}
		}
    // 将当前的TransactionInfo信息绑定到线程上
		return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
	}

从这个方法我们可以看出如果txAttr为null,那么我们将不会开启事务。如果不为 null,那么将会获取一个连接并开启事务:

  • 1、使用tm.getTransaction(txAttr)获取连接并开启事务
  • 2、使用prepareTransactionInfo(tm, txAttr, joinpointIdentification, status)将事务信息绑定到对应的线程

4、getTransaction() 源码分析

	//org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
	@Override
	public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {

		// Use defaults if no transaction definition given.
		TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
		
        //获取事务对象  该对象可能已经开启事务,如果开启则进入handleExistingTrransaction方法
        //否则将进入
		Object transaction = doGetTransaction();
		boolean debugEnabled = logger.isDebugEnabled();
		
        //当前调用已经存在事务的处理逻辑 
		if (isExistingTransaction(transaction)) {
			// Existing transaction found -> check propagation behavior to find out how to behave.
			return handleExistingTrransaction(def, transaction, debugEnabled);
		}

		// Check definition settings for new transaction.
		if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
			throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
		}

		// No existing transaction found -> check propagation behavior to find out how to proceed.
		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) {
			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);
			}
			boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
			return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
		}
	}

getTransaction()方法主要将事件委派给三个方法进行处理

  • 1、doGetTransaction() 用来获取事务对象
  • 2、判断当前的事务对象是否已经开启了事务,如果已经开启了事务,那么则调用handleExistingTrransaction()方法进行处理
  • 3、如果没有开启事务,则调用startTransaction()开启一一个新的事务。

接下来继续分析上面提到的三个方法:

5、doGetTransaction()方法分析

	//org.springframework.jdbc.datasource.DataSourceTransactionManager#doGetTransaction
	@Override
	protected Object doGetTransaction() {
        //新建一个事务对象   
		DataSourceTransactionObject txObject = new DataSourceTransactionObject();
		txObject.setSavepointAllowed(isNestedTransactionAllowed());
        //从当前线程中获取数据库连接对象  
		ConnectionHolder conHolder =
				(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
        //将连接对象设置到事务对象中
		txObject.setConnectionHolder(conHolder, false);
		return txObject;
	}

doGetTransaction()方法的主要功能是新建一个事务对象,并且将从当前线程中获取到的数据库连接对象设置到事务对象中。

6、handleExistingTrransaction()方法分析

通过isExistingTransaction()方法来判定当前事务已经开启了事务,如果开启才会调用handleExistingTrransaction()方法来进行处理。

//org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction	
/**
	 * Create a TransactionStatus for an existing transaction.
	 */
	private TransactionStatus handleExistingTransaction(
			TransactionDefinition definition, Object transaction, boolean debugEnabled)
			throws TransactionException {

		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
			throw new IllegalTransactionStateException(
					"Existing transaction found for transaction marked with propagation 'never'");
		}

		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
			if (debugEnabled) {
				logger.debug("Suspending current transaction");
			}
			Object suspendedResources = suspend(transaction);
			boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
			return prepareTransactionStatus(
					definition, null, false, newSynchronization, debugEnabled, suspendedResources);
		}

		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
			if (debugEnabled) {
				logger.debug("Suspending current transaction, creating new transaction with name [" +
						definition.getName() + "]");
			}
			SuspendedResourcesHolder suspendedResources = suspend(transaction);
			try {
				return startTransaction(definition, transaction, debugEnabled, suspendedResources);
			}
			catch (RuntimeException | Error beginEx) {
				resumeAfterBeginException(transaction, suspendedResources, beginEx);
				throw beginEx;
			}
		}

		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
			if (!isNestedTransactionAllowed()) {
				throw new NestedTransactionNotSupportedException(
						"Transaction manager does not allow nested transactions by default - " +
						"specify 'nestedTransactionAllowed' property with value 'true'");
			}
			if (debugEnabled) {
				logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
			}
			if (useSavepointForNestedTransaction()) {
				// Create savepoint within existing Spring-managed transaction,
				// through the SavepointManager API implemented by TransactionStatus.
				// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
				DefaultTransactionStatus status =
						prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
				status.createAndHoldSavepoint();
				return status;
			}
			else {
				// Nested transaction through nested begin and commit/rollback calls.
				// Usually only for JTA: Spring synchronization might get activated here
				// in case of a pre-existing JTA transaction.
				return startTransaction(definition, transaction, debugEnabled, null);
			}
		}

		// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
		if (debugEnabled) {
			logger.debug("Participating in existing transaction");
		}
		if (isValidateExistingTransaction()) {
			if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
				Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
				if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
					Constants isoConstants = DefaultTransactionDefinition.constants;
					throw new IllegalTransactionStateException("Participating transaction with definition [" +
							definition + "] specifies isolation level which is incompatible with existing transaction: " +
							(currentIsolationLevel != null ?
									isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
									"(unknown)"));
				}
			}
			if (!definition.isReadOnly()) {
				if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
					throw new IllegalTransactionStateException("Participating transaction with definition [" +
							definition + "] is not marked as read-only but existing transaction is");
				}
			}
		}
		boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
		return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
	}

该方法主要对应我们所说的事务的传播行为,我们可以看出,当一个事务存在的时候。根据不同的事务传播行为,做出相应的处理。

7、startTransaction()方法解析

有下面两种情况会调用该方法:

  • 1、在当前没有存在事务的情况下,且事务的传播行为为:PROPAGATION_REQUIRED(默认传播行为,有就使用原先的,没有就新建一个)、PROPAGATION_REQUIRES_NEW(始终新建一个事务)、PROPAGATION_NESTED(始终以内嵌的事务执行)
  • 2、存在事务的情况下,且事务传播行为是:PROPAGATION_NESTED(始终以内嵌的事务执行)。

接下来我们看看startTransaction()的源码

	/**
	 * Start a new transaction.
	 */
	private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
			boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {

		boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
		DefaultTransactionStatus status = newTransactionStatus(
				definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
        //核心方法,获取数据库的连接及
		doBegin(transaction, definition);
		prepareSynchronization(status, definition);
		return status;
	}
	/**
	
	org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
	 * This implementation sets the isolation level but ignores the timeout.
	 */
	@Override
	protected void doBegin(Object transaction, TransactionDefinition definition) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		Connection con = null;

		try {
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                //从当前的数据源中获取到一个数据库连接
				Connection newCon = obtainDataSource().getConnection();
				if (logger.isDebugEnabled()) {
					logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
				}
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			}

			txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
			con = txObject.getConnectionHolder().getConnection();

			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
			txObject.setPreviousIsolationLevel(previousIsolationLevel);
			txObject.setReadOnly(definition.isReadOnly());

			// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
			// so we don't want to do it unnecessarily (for example if we've explicitly
			// configured the connection pool to set it already).
			if (con.getAutoCommit()) {
				txObject.setMustRestoreAutoCommit(true);
				if (logger.isDebugEnabled()) {
					logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
				}
				con.setAutoCommit(false);
			}

			prepareTransactionalConnection(con, definition);
			txObject.getConnectionHolder().setTransactionActive(true);

			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			}

			// Bind the connection holder to the thread.
			if (txObject.isNewConnectionHolder()) {
                //将数据库连接 绑定到当前线程
				TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
			}
		}

		catch (Throwable ex) {
			if (txObject.isNewConnectionHolder()) {
				DataSourceUtils.releaseConnection(con, obtainDataSource());
				txObject.setConnectionHolder(null, false);
			}
			throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
		}
	}

doBegin()方法主要做了这两件事情:

  • 1、通过obtainDataSource().getConnection() 获取数据库连接
  • 2、通过TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());将数据库的连接绑定到当前线程

至此事务调用的前置准备工作已经全部完成。

8、invocation.proceedWithInvocation();方法来调用目标方法。

这里就是我们使用@Transactional标记的方法

9、存在异常,则进入completeTransactionAfterThrowing()来进行处理

	protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
						"] after exception: " + ex);
			}
			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
				try {
					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
				}
				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;
				}
			}
			else {
				// We don't roll back on this exception.
				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
				try {
					txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
				}
				catch (TransactionSystemException ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					ex2.initApplicationException(ex);
					throw ex2;
				}
				catch (RuntimeException | Error ex2) {
					logger.error("Application exception overridden by commit exception", ex);
					throw ex2;
				}
			}
		}
	}

该方法的主要作用是:

首先通过txInfo.transactionAttribute.rollbackOn(ex)判断当前的异常是否需要回滚,如果返会true就进行回滚,否则提交当前事务。

10、清空设置在线程上的transatcionInfo信息

protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {
		if (txInfo != null) {
			txInfo.restoreThreadLocalStatus();
		}
	}

11、完成事务的提交动作

	protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
		if (txInfo != null && txInfo.getTransactionStatus() != null) {
			if (logger.isTraceEnabled()) {
				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
			}
			txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
		}
	}

在commit(txInfo.getTransactionStatus())方法中

	@Override
	public final void commit(TransactionStatus status) throws TransactionException {
		if (status.isCompleted()) {
			throw new IllegalTransactionStateException(
					"Transaction is already completed - do not call commit or rollback more than once per transaction");
		}

		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
		if (defStatus.isLocalRollbackOnly()) {
			if (defStatus.isDebug()) {
				logger.debug("Transactional code has requested rollback");
			}
			processRollback(defStatus, false);
			return;
		}

		if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
			if (defStatus.isDebug()) {
				logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
			}
			processRollback(defStatus, true);
			return;
		}

		processCommit(defStatus);
	}

从上面的方法我们可以看出,如果是一个只读事务,那么将进行回滚操作。否则进行提交的,并从当前线程上解除connection的绑定。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值