流程图说明
spring事务-详细
配置类
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);
}
}
测试方法
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 +
'}';
}
}
主启动类
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();;
}
}
}
建表语句
create table t_test
(
id int auto_increment
primary key,
name varchar(32) null,
age double null
);
三个属性表示的意思都是和代理对象创建有关系,此外还得注意到,他导入了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,事务实现相关的类。
导入TransactionManagementConfigurationSelector
,他本质上是一个ImportSelector
,通过String[] selectImports(AnnotationMetadata)
返回的全限定类名,可以解析这些类。
在它里面,主要是通过mode()
来确定那种那种事务模式。
导入了事务的配置。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对象。
换句话来说,Spring是怎么知道标注了@Transaction注解的方法需要创建代理对象。
前面说了,会导入AutoProxyRegistrar
,它会导入AbstractAutoProxyCreator
来创建代理对象,在创建代理对象的时候会拿到所有的Advisor,做匹配。相关方法在 AbstractAdvisorAutoProxyCreator#findAdvisorsThatCanApply
里面。
前面说了,@EnableTransactionManager会导入BeanFactoryTransactionAttributeSourceAdvisor 。他是一个Advisor,在匹配的时候会拿到他的ClassFilter做匹配,在BeanFactoryTransactionAttributeSourceAdvisor里面,classFilter如下所示
一般来说,在做匹配的过程中,先匹配ClassFilter,在做MethodMatcher的匹配。
最终会调用到TransactionAttributeSource
,在创建BeanFactoryTransactionAttributeSourceAdvisor的时候就已经设置进来了,在TransactionAttributeSource里面,调用TransactionAnnotationParser
来做判断解析操作,返回为true表示,匹配,就可以创建代理对象了,给代理对象应用的MethodIntercept是TransactionInterceptor
要回答这个问题,直接看@Transaction注解propagation
属性对应的枚举值。总共有下面的几种类型
REQUIRED
,required,如果没有事务,就创建一个新的事务,如果有,就支持(同用已经存在的),
SUPPORTS
,supports,没有事务,就以没有事务的方式来操作,如果有,就支持(同用已经存在的)。
MANDATORY
,mandatory,必须的有事务,没有事务就报错,如果有,就支持(同用已经存在的)。
REQUIRES_NEW
,require_new,创建一个新的事务,如果当前有事务,就挂起当前事务。(需要注意的是,这个操作并不是完全适用于所有的TransactionManager,需要使用JtaTransactionManager并且需要的事务得是javax.transaction.TransactionManager)
NOT_SUPPORTED
,not_supported,不支持事务,如果当前有事务,就挂起当前事务,以非事务的方式来运行,(需要注意的是,这个操作并不是完全适用于所有的TransactionManager,需要使用JtaTransactionManager并且需要的事务得是javax.transaction.TransactionManager)
NEVER
,never,不支持事务,按照非事务的方式运行,如果有当前有存在事务就报错。
NESTED
,nested,嵌套事务,如果当前有事务存在,执行嵌套事务,不存在就会创建一个新的事务执行就像(REQUIRED),嵌套事务的实际创建只能在特定的事务管理器上工作,这只适用于JDBC的DataSourceTransactionManager。
内层的事务的回滚不会影响外层的事务,外层事务的回滚会影响内层事务。
默认的事务的传播级别是 REQUIRED
。
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#invoke
方法上,按照上面的例子,先跑起来就会看到下面的代码(TransactionAspectSupport
)
这就是事务的核心代码,可以慢慢的看。
TransactionAttributeSource
是用来检索事务的元信息的。比如事务的传播级别,隔离级别等等。
只要干了下面的几个事情:
通过TransactionAttributeSource
来获取当前方法或者类上的注解的元信息,封装为TransactionAttribute
。
找到PlatformTransactionManager
。
创建TransactionInfo
对象,在这个对象里面包含了上一个事务的信息(如果需要就是挂起的资源)当前事务的状态(是否有savePoint,是否是isRollbackOnly
),事务的元信息,事务管理者。当前的连接点。
调用目标方法
处理异常,如果需要回滚就回滚,有savePoint就回滚到SavePoint,没有匹配到就提交。
恢复上一个事务的信息。
提交当前的事务。
@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的属性发现,前几个基本一开始就确定好了,唯独TransactionStatus
和TransactionInfo
不能确定。下面的部分得分为这两部分来看
TransactionStatus
它是通过AbstractPlatformTransactionManager#getTransaction(TransactionDefinition)
来创建的,这里的代码比较复杂。代码如下:
主要的逻辑如下:
PROPAGATION_MANDATORY
就直接报错。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
获取事务(doGetTransaction())和判断是否有事务(isExistingTransaction(Object))
创建DataSourceTransactionObject
对象,它里面主要包含了ConnectionHolder
对象,ConnectionHolder包含了Connection和一些标志位,比如事务是否活动。
获取当前的DataSoruce,从ThreadLocal中获取一个Map,从Map中检索是否有ConnectionHolder对象(具体代码在TransactionSynchronizationManager#getResource(Object)
),有的话,就说明之前有事务,没有就没有。
在创建好的DataSourceTransactionObject中获取ConnectionHolder对象,ConnectionHolder不是null并且事务是活动的,说明当前有事务存在。
开启一个事务(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已经创建好了
这里就一次性将挂起也说了吧
在构建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()
它是为了事务执行的时候同步回调的。里面的方法对应着事务执行的各个阶段。可以通过这个接口让普通的方法也具有事务性质,比如你需要在一个事务的方法里面做一些操作,比如请求外部接口,等等,可以通过实现这个接口,并且将它添加到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) {
}
}
上面黄色背景的说的就是这个,他里面包含了当前事务的信息。
上面说的那些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是一个boolean值,用它来控制是否要回滚,尤其是在长事务中,至关重要。理论上来说,
他是事务提交操作(AbstractPlatformTransactionManager#commit(TransactionStatus))里面的一个前置的增强的操作,在没有异常的情况下,也可以回滚事务。
先看Spring事务中有几个地方用到了这个。
DefaultTransactionStatus#rollbackOnly属性
这就要说到一个应用的范围了,DefaultTransactionStatus准确的说,应该是当前事务的状态,也就是说表示的当前事务的状态,只有有@Transactional注解标注的方法, 当他生效的时候每次都会创建一个新的DefaultTransactionStatus对象,他的应用范围最小。但是得先判断
AbstractPlatformTransactionManager#shouldCommitOnGlobalRollbackOnly()方法
他是一个全局的,是属于当前的事务管理器的
SmartTransactionObject#isRollbackOnly()
他是真正起作用的事务,他里面包含了Connection,谁有Connection的引用谁才最厉害。虽然 DataSourceTransactionObject最后也是作为DefaultTransactionStatus的一个属性,不好区分,但是可以增加标志位(newTransaction来表示有没有创建新的事务)
AbstractPlatformTransactionManager#failEarlyOnGlobalRollbackOnly属性
他表示不需要抛出异常。
这种机制用在长事务里面就很方便,如果在A->B的调用链路里面,A在调用B的时候处理了异常,如果他俩的传播级别都是REQUIRED
,A就不会捕获到异常,如果没有这个机制,A就会将事务提交,从而导致B里面的错误数据也提交。这就是问题。有了这个机制,在B报错之后,(TransactionInterceptor回捕获到异常,设置SmartTransactionObject的rollbackOnly),A执行完了之后,在提交事务的时候,发现标志位为true了,就得回滚,从而避免了产生上面说的问题。