一、准备工作,环境搭建
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的绑定。