当前位置: 首页 > news >正文

Spring源码解析——事务增强器

正文

上一篇文章我们讲解了事务的Advisor是如何注册进Spring容器的,也讲解了Spring是如何将有配置事务的类配置上事务的,实际上也就是用了AOP那一套,也讲解了Advisor,pointcut验证流程,至此,事务的初始化工作都已经完成了,在之后的调用过程,如果代理类的方法被调用,都会调用BeanFactoryTransactionAttributeSourceAdvisor这个Advisor的增强方法,也就是我们还未提到的那个Advisor里面的advise,还记得吗,在自定义标签的时候我们将TransactionInterceptor这个Advice作为bean注册进IOC容器,并且将其注入进Advisor中,这个Advice在代理类的invoke方法中会被封装到拦截器链中,最终事务的功能都在advise中体现,所以我们先来关注一下TransactionInterceptor这个类吧。最全面的Java面试网站

TransactionInterceptor类继承自MethodInterceptor,所以调用该类是从其invoke方法开始的,首先预览下这个方法:

@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {// Work out the target class: may be {@code null}.// The TransactionAttributeSource should be passed the target class// as well as the method, which may be from an interface.Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction...return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

重点来了,进入invokeWithinTransaction方法:

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {// If the transaction attribute is null, the method is non-transactional.TransactionAttributeSource tas = getTransactionAttributeSource();// 获取对应事务属性final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);// 获取beanFactory中的transactionManagerfinal PlatformTransactionManager tm = determineTransactionManager(txAttr);// 构造方法唯一标识(类.方法,如:service.UserServiceImpl.save)final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);// 声明式事务处理if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {// 创建TransactionInfoTransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);Object retVal = null;try {// 执行原方法// 继续调用方法拦截器链,这里一般将会调用目标类的方法,如:AccountServiceImpl.save方法retVal = invocation.proceedWithInvocation();}catch (Throwable ex) {// 异常回滚completeTransactionAfterThrowing(txInfo, ex);// 手动向上抛出异常,则下面的提交事务不会执行// 如果子事务出异常,则外层事务代码需catch住子事务代码,不然外层事务也会回滚throw ex;}finally {// 消除信息cleanupTransactionInfo(txInfo);}// 提交事务commitTransactionAfterReturning(txInfo);return retVal;}else {final ThrowableHolder throwableHolder = new ThrowableHolder();try {// 编程式事务处理Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);try {return invocation.proceedWithInvocation();}catch (Throwable ex) {if (txAttr.rollbackOn(ex)) {// A RuntimeException: will lead to a rollback.if (ex instanceof RuntimeException) {throw (RuntimeException) ex;}else {throw new ThrowableHolderException(ex);}}else {// A normal return value: will lead to a commit.throwableHolder.throwable = ex;return null;}}finally {cleanupTransactionInfo(txInfo);}});// Check result state: It might indicate a Throwable to rethrow.if (throwableHolder.throwable != null) {throw throwableHolder.throwable;}return result;}catch (ThrowableHolderException ex) {throw ex.getCause();}catch (TransactionSystemException ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);ex2.initApplicationException(throwableHolder.throwable);}throw ex2;}catch (Throwable ex2) {if (throwableHolder.throwable != null) {logger.error("Application exception overridden by commit exception", throwableHolder.throwable);}throw ex2;}}
}

创建事务Info对象

我们先分析事务创建的过程。

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {// If no name specified, apply method identification as transaction name.// 如果没有名称指定则使用方法唯一标识,并使用DelegatingTransactionAttribute封装txAttrif (txAttr != null && txAttr.getName() == null) {txAttr = new DelegatingTransactionAttribute(txAttr) {@Overridepublic String getName() {return joinpointIdentification;}};}TransactionStatus status = null;if (txAttr != null) {if (tm != null) {// 获取TransactionStatusstatus = tm.getTransaction(txAttr);}else {if (logger.isDebugEnabled()) {logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +"] because no transaction manager has been configured");}}}// 根据指定的属性与status准备一个TransactionInforeturn prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

对于createTransactionlfNecessary函数主要做了这样几件事情。

(1)使用 DelegatingTransactionAttribute 封装传入的 TransactionAttribute 实例。

对于传入的TransactionAttribute类型的参数txAttr,当前的实际类型是RuleBasedTransactionAttribute,是由获取事务属性时生成,主要用于数据承载,而这里之所以使用DelegatingTransactionAttribute进行封装,当然是提供了更多的功能。

(2)获取事务。

事务处理当然是以事务为核心,那么获取事务就是最重要的事情。

(3)构建事务信息。

根据之前几个步骤获取的信息构建Transactionlnfo并返回。

分享一份大彬精心整理的大厂面试手册,包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题,非常实用,有小伙伴靠着这份手册拿过字节offer~

需要的小伙伴可以自行下载

http://mp.weixin.qq.com/s?__biz=Mzg2OTY1NzY0MQ==&mid=2247485445&idx=1&sn=1c6e224b9bb3da457f5ee03894493dbc&chksm=ce98f543f9ef7c55325e3bf336607a370935a6c78dbb68cf86e59f5d68f4c51d175365a189f8#rd

获取事务

其中核心是在getTransaction方法中:

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {// 获取一个transactionObject transaction = doGetTransaction();boolean debugEnabled = logger.isDebugEnabled();if (definition == null) {definition = new DefaultTransactionDefinition();}// 如果在这之前已经存在事务了,就进入存在事务的方法中if (isExistingTransaction(transaction)) {return handleExistingTransaction(definition, transaction, debugEnabled);}// 事务超时设置验证if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());}// 走到这里说明此时没有存在事务,如果传播特性是MANDATORY时抛出异常if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");}// 如果此时不存在事务,当传播特性是REQUIRED或NEW或NESTED都会进入if语句块else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {// PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED都需要新建事务// 因为此时不存在事务,将null挂起SuspendedResourcesHolder suspendedResources = suspend(null);if (debugEnabled) {logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);}try {boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);// new一个status,存放刚刚创建的transaction,然后将其标记为新事务!// 这里transaction后面一个参数决定是否是新事务!DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);// 新开一个连接的地方,非常重要doBegin(transaction, definition);prepareSynchronization(status, definition);return status;}catch (RuntimeException | Error ex) {resume(null, suspendedResources);throw ex;}}else {// Create "empty" transaction: no actual transaction, but potentially synchronization.if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {logger.warn("Custom isolation level specified but no actual transaction initiated; " +"isolation level will effectively be ignored: " + definition);}// 其他的传播特性一律返回一个空事务,transaction = null//当前不存在事务,且传播机制=PROPAGATION_SUPPORTS/PROPAGATION_NOT_SUPPORTED/PROPAGATION_NEVER,这三种情况,创建“空”事务boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);}
}

先来看看transaction是如何被创建出来的:

@Override
protected Object doGetTransaction() {// 这里DataSourceTransactionObject是事务管理器的一个内部类// DataSourceTransactionObject就是一个transaction,这里new了一个出来DataSourceTransactionObject txObject = new DataSourceTransactionObject();txObject.setSavepointAllowed(isNestedTransactionAllowed());// 解绑与绑定的作用在此时体现,如果当前线程有绑定的话,将会取出holder// 第一次conHolder肯定是nullConnectionHolder conHolder =(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());// 此时的holder被标记成一个旧holdertxObject.setConnectionHolder(conHolder, false);return txObject;
}

创建transaction过程很简单,接着就会判断当前是否存在事务:

@Override
protected boolean isExistingTransaction(Object transaction) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}public boolean hasConnectionHolder() {return (this.connectionHolder != null);
}

这里判断是否存在事务的依据主要是获取holder中的transactionActive变量是否为true,如果是第一次进入事务,holder直接为null判断不存在了,如果是第二次进入事务transactionActive变量是为true的(后面会提到是在哪里把它变成true的),由此来判断当前是否已经存在事务了。

至此,源码分成了2条处理线:

1.当前已存在事务:isExistingTransaction()判断是否存在事务,存在事务handleExistingTransaction()根据不同传播机制不同处理

2.当前不存在事务: 不同传播机制不同处理

当前不存在事务

如果不存在事务,传播特性又是REQUIRED或NEW或NESTED,将会先挂起null,这个挂起方法我们后面再讲,然后创建一个DefaultTransactionStatus ,并将其标记为新事务,然后执行doBegin(transaction, definition);这个方法也是一个关键方法

神秘又关键的status对象

TransactionStatus接口

public interface TransactionStatus extends SavepointManager, Flushable {// 返回当前事务是否为新事务(否则将参与到现有事务中,或者可能一开始就不在实际事务中运行)boolean isNewTransaction();// 返回该事务是否在内部携带保存点,也就是说,已经创建为基于保存点的嵌套事务。boolean hasSavepoint();// 设置事务仅回滚。void setRollbackOnly();// 返回事务是否已标记为仅回滚boolean isRollbackOnly();// 将会话刷新到数据存储区@Overridevoid flush();// 返回事物是否已经完成,无论提交或者回滚。boolean isCompleted();
}

再来看看实现类DefaultTransactionStatus

DefaultTransactionStatus

public class DefaultTransactionStatus extends AbstractTransactionStatus {//事务对象@Nullableprivate final Object transaction;//事务对象private final boolean newTransaction;private final boolean newSynchronization;private final boolean readOnly;private final boolean debug;//事务对象@Nullableprivate final Object suspendedResources;public DefaultTransactionStatus(@Nullable Object transaction, boolean newTransaction, boolean newSynchronization,boolean readOnly, boolean debug, @Nullable Object suspendedResources) {this.transaction = transaction;this.newTransaction = newTransaction;this.newSynchronization = newSynchronization;this.readOnly = readOnly;this.debug = debug;this.suspendedResources = suspendedResources;}//略...
}

我们看看这行代码 DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

// 这里是构造一个status对象的方法
protected DefaultTransactionStatus newTransactionStatus(TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {boolean actualNewSynchronization = newSynchronization &&!TransactionSynchronizationManager.isSynchronizationActive();return new DefaultTransactionStatus(transaction, newTransaction, actualNewSynchronization,definition.isReadOnly(), debug, suspendedResources);
}

实际上就是封装了事务属性definition,新创建的**transaction,**并且将事务状态属性设置为新事务,最后一个参数为被挂起的事务。

简单了解一下关键参数即可:

第二个参数transaction:事务对象,在一开头就有创建,其就是事务管理器的一个内部类。

第三个参数newTransaction:布尔值,一个标识,用于判断是否是新的事务,用于提交或者回滚方法中,是新的才会提交或者回滚。

最后一个参数suspendedResources:被挂起的对象资源,挂起操作会返回旧的holder,将其与一些事务属性一起封装成一个对象,就是这个suspendedResources这个对象了,它会放在status中,在最后的清理工作方法中判断status中是否有这个挂起对象,如果有会恢复它

接着我们来看看关键代码 doBegin(transaction, definition);

 @Overrideprotected void doBegin(Object transaction, TransactionDefinition definition) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;Connection con = null;try {// 判断如果transaction没有holder的话,才去从dataSource中获取一个新连接if (!txObject.hasConnectionHolder() ||txObject.getConnectionHolder().isSynchronizedWithTransaction()) {//通过dataSource获取连接Connection newCon = this.dataSource.getConnection();if (logger.isDebugEnabled()) {logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");}// 所以,只有transaction中的holder为空时,才会设置为新holder// 将获取的连接封装进ConnectionHolder,然后封装进transaction的connectionHolder属性txObject.setConnectionHolder(new ConnectionHolder(newCon), true);}//设置新的连接为事务同步中txObject.getConnectionHolder().setSynchronizedWithTransaction(true);con = txObject.getConnectionHolder().getConnection();//conn设置事务隔离级别,只读Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);txObject.setPreviousIsolationLevel(previousIsolationLevel);//DataSourceTransactionObject设置事务隔离级别// 如果是自动提交切换到手动提交if (con.getAutoCommit()) {txObject.setMustRestoreAutoCommit(true);if (logger.isDebugEnabled()) {logger.debug("Switching JDBC Connection [" + con + "] to manual commit");}con.setAutoCommit(false);}// 如果只读,执行sql设置事务只读prepareTransactionalConnection(con, definition);// 设置connection持有者的事务开启状态txObject.getConnectionHolder().setTransactionActive(true);int timeout = determineTimeout(definition);if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {// 设置超时秒数txObject.getConnectionHolder().setTimeoutInSeconds(timeout);}// 将当前获取到的连接绑定到当前线程if (txObject.isNewConnectionHolder()) {TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());}}catch (Throwable ex) {if (txObject.isNewConnectionHolder()) {DataSourceUtils.releaseConnection(con, this.dataSource);txObject.setConnectionHolder(null, false);}throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);}}

conn设置事务隔离级别

@Nullable
public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition)throws SQLException {Assert.notNull(con, "No Connection specified");// Set read-only flag.// 设置数据连接的只读标识if (definition != null && definition.isReadOnly()) {try {if (logger.isDebugEnabled()) {logger.debug("Setting JDBC Connection [" + con + "] read-only");}con.setReadOnly(true);}catch (SQLException | RuntimeException ex) {Throwable exToCheck = ex;while (exToCheck != null) {if (exToCheck.getClass().getSimpleName().contains("Timeout")) {// Assume it's a connection timeout that would otherwise get lost: e.g. from JDBC 4.0throw ex;}exToCheck = exToCheck.getCause();}// "read-only not supported" SQLException -> ignore, it's just a hint anywaylogger.debug("Could not set JDBC Connection read-only", ex);}}// Apply specific isolation level, if any.// 设置数据库连接的隔离级别Integer previousIsolationLevel = null;if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {if (logger.isDebugEnabled()) {logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +definition.getIsolationLevel());}int currentIsolation = con.getTransactionIsolation();if (currentIsolation != definition.getIsolationLevel()) {previousIsolationLevel = currentIsolation;con.setTransactionIsolation(definition.getIsolationLevel());}}return previousIsolationLevel;
}

我们看到都是通过 Connection 去设置

线程变量的绑定

我们看 doBegin 方法的47行,**将当前获取到的连接绑定到当前线程,绑定与解绑围绕一个线程变量,此变量在TransactionSynchronizationManager**类中:

private static final ThreadLocal<Map<Object, Object>> resources =  new NamedThreadLocal<>("Transactional resources");

这是一个 static final 修饰的 线程变量,存储的是一个Map,我们来看看47行的静态方法,bindResource

public static void bindResource(Object key, Object value) throws IllegalStateException {// 从上面可知,线程变量是一个Map,而这个Key就是dataSource// 这个value就是holderObject actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);Assert.notNull(value, "Value must not be null");// 获取这个线程变量MapMap<Object, Object> map = resources.get();// set ThreadLocal Map if none foundif (map == null) {map = new HashMap<>();resources.set(map);}// 将新的holder作为value,dataSource作为key放入当前线程Map中Object oldValue = map.put(actualKey, value);// Transparently suppress a ResourceHolder that was marked as void...if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {oldValue = null;}if (oldValue != null) {throw new IllegalStateException("Already value [" + oldValue + "] for key [" +actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");}    Thread.currentThread().getName() + "]");}// 略...
}

扩充知识点

这里再扩充一点,mybatis中获取的数据库连接,就是根据 dataSource 从ThreadLocal中获取的

以查询举例,会调用Executor#doQuery方法:

最终会调用DataSourceUtils#doGetConnection获取,真正的数据库连接,其中TransactionSynchronizationManager中保存的就是方法调用前,spring增强方法中绑定到线程的connection,从而保证整个事务过程中connection的一致性

我们看看TransactionSynchronizationManager.getResource(Object key)这个方法

@Nullable
public static Object getResource(Object key) {Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);Object value = doGetResource(actualKey);if (value != null && logger.isTraceEnabled()) {logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +Thread.currentThread().getName() + "]");}return value;
}@Nullable
private static Object doGetResource(Object actualKey) {Map<Object, Object> map = resources.get();if (map == null) {return null;}Object value = map.get(actualKey);// Transparently remove ResourceHolder that was marked as void...if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {map.remove(actualKey);// Remove entire ThreadLocal if empty...if (map.isEmpty()) {resources.remove();}value = null;}return value;
}

就是从线程变量的Map中根据 DataSource获取 ConnectionHolder

已经存在的事务

前面已经提到,第一次事务开始时必会新创一个holder然后做绑定操作,此时线程变量是有holder的且avtive为true,如果第二个事务进来,去new一个transaction之后去线程变量中取holder,holder是不为空的且active是为true的,所以会进入handleExistingTransaction方法:

 private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled)throws TransactionException {// 1.NERVER(不支持当前事务;如果当前事务存在,抛出异常)报错if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {throw new IllegalTransactionStateException("Existing transaction found for transaction marked with propagation 'never'");}// 2.NOT_SUPPORTED(不支持当前事务,现有同步将被挂起)挂起当前事务,返回一个空事务if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {if (debugEnabled) {logger.debug("Suspending current transaction");}// 这里会将原来的事务挂起,并返回被挂起的对象Object suspendedResources = suspend(transaction);boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);// 这里可以看到,第二个参数transaction传了一个空事务,第三个参数false为旧标记// 最后一个参数就是将前面挂起的对象封装进新的Status中,当前事务执行完后,就恢复suspendedResourcesreturn prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources);}// 3.REQUIRES_NEW挂起当前事务,创建新事务if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {if (debugEnabled) {logger.debug("Suspending current transaction, creating new transaction with name [" +definition.getName() + "]");}// 将原事务挂起,此时新建事务,不与原事务有关系// 会将transaction中的holder设置为null,然后解绑!SuspendedResourcesHolder suspendedResources = suspend(transaction);try {boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);// new一个status出来,传入transaction,并且为新事务标记,然后传入挂起事务DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);// 这里也做了一次doBegin,此时的transaction中holer是为空的,因为之前的事务被挂起了// 所以这里会取一次新的连接,并且绑定!doBegin(transaction, definition);prepareSynchronization(status, definition);return status;}catch (RuntimeException beginEx) {resumeAfterBeginException(transaction, suspendedResources, beginEx);throw beginEx;}catch (Error beginErr) {resumeAfterBeginException(transaction, suspendedResources, beginErr);throw beginErr;}}// 如果此时的传播特性是NESTED,不会挂起事务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() + "]");}// 这里如果是JTA事务管理器,就不可以用savePoint了,将不会进入此方法if (useSavepointForNestedTransaction()) { // 这里不会挂起事务,说明NESTED的特性是原事务的子事务而已// new一个status,传入transaction,传入旧事务标记,传入挂起对象=nullDefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);// 这里是NESTED特性特殊的地方,在先前存在事务的情况下会建立一个savePointstatus.createAndHoldSavepoint();return status;}else {// JTA事务走这个分支,创建新事务boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);doBegin(transaction, definition);prepareSynchronization(status, definition);return status;}}// 到这里PROPAGATION_SUPPORTS 或 PROPAGATION_REQUIRED或PROPAGATION_MANDATORY,存在事务加入事务即可,标记为旧事务,空挂起boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);}

对于已经存在事务的处理过程中,我们看到了很多熟悉的操作,但是,也有些不同的地方,函数中对已经存在的事务处理考虑两种情况。

(1)PROPAGATION_REQUIRES_NEW表示当前方法必须在它自己的事务里运行,一个新的事务将被启动,而如果有一个事务正在运行的话,则在这个方法运行期间被挂起。而Spring中对于此种传播方式的处理与新事务建立最大的不同点在于使用suspend方法将原事务挂起。 将信息挂起的目的当然是为了在当前事务执行完毕后在将原事务还原。

(2)PROPAGATION_NESTED表示如果当前正有一个事务在运行中,则该方法应该运行在一个嵌套的事务中,被嵌套的事务可以独立于封装事务进行提交或者回滚,如果封装事务不存在,行为就像PROPAGATION_REQUIRES_NEW。对于嵌入式事务的处理,Spring中主要考虑了两种方式的处理。

  • Spring中允许嵌入事务的时候,则首选设置保存点的方式作为异常处理的回滚。
  • 对于其他方式,比如JTA无法使用保存点的方式,那么处理方式与PROPAGATION_ REQUIRES_NEW相同,而一旦出现异常,则由Spring的事务异常处理机制去完成后续操作。

对于挂起操作的主要目的是记录原有事务的状态,以便于后续操作对事务的恢复

小结

到这里我们可以知道,在当前存在事务的情况下,根据传播特性去决定是否为新事务,是否挂起当前事务。

NOT_SUPPORTED :会挂起事务,不运行doBegin方法传空transaction,标记为旧事务。封装status对象:

return prepareTransactionStatus(definition, null, false, newSynchronization, debugEnabled, suspendedResources)

REQUIRES_NEW :将会挂起事务且运行doBegin方法,标记为新事务。封装status对象:

DefaultTransactionStatus status = newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

NESTED :不会挂起事务且不会运行doBegin方法,标记为旧事务,但会创建savePoint。封装status对象:

DefaultTransactionStatus status =prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);

其他事务例如REQUIRED :不会挂起事务,封装原有的transaction不会运行doBegin方法,标记旧事务,封装status对象:

return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);

挂起

对于挂起操作的主要目的是记录原有事务的状态,以便于后续操作对事务的恢复:

@Nullable
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {if (TransactionSynchronizationManager.isSynchronizationActive()) {List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();try {Object suspendedResources = null;if (transaction != null) {// 这里是真正做挂起的方法,这里返回的是一个holdersuspendedResources = doSuspend(transaction);}// 这里将名称、隔离级别等信息从线程变量中取出并设置对应属性为null到线程变量String name = TransactionSynchronizationManager.getCurrentTransactionName();TransactionSynchronizationManager.setCurrentTransactionName(null);boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();TransactionSynchronizationManager.setActualTransactionActive(false);// 将事务各个属性与挂起的holder一并封装进SuspendedResourcesHolder对象中return new SuspendedResourcesHolder(suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);}catch (RuntimeException | Error ex) {// doSuspend failed - original transaction is still active...doResumeSynchronization(suspendedSynchronizations);throw ex;}}else if (transaction != null) {// Transaction active but no synchronization active.Object suspendedResources = doSuspend(transaction);return new SuspendedResourcesHolder(suspendedResources);}else {// Neither transaction nor synchronization active.return null;}
}
@Override
protected Object doSuspend(Object transaction) {DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;// 将transaction中的holder属性设置为空txObject.setConnectionHolder(null);// ConnnectionHolder从线程变量中解绑!return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}

我们来看看 unbindResource

private static Object doUnbindResource(Object actualKey) {// 取得当前线程的线程变量MapMap<Object, Object> map = resources.get();if (map == null) {return null;}// 将key为dataSourece的value移除出Map,然后将旧的Holder返回Object value = map.remove(actualKey);// Remove entire ThreadLocal if empty...// 如果此时map为空,直接清除线程变量if (map.isEmpty()) {resources.remove();}// Transparently suppress a ResourceHolder that was marked as void...if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {value = null;}if (value != null && logger.isTraceEnabled()) {logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +Thread.currentThread().getName() + "]");}// 将旧Holder返回return value;
}

可以回头看一下解绑操作的介绍。这里挂起主要干了三件事:

  1. 将transaction中的holder属性设置为空
  2. 解绑(会返回线程中的那个旧的holder出来,从而封装到SuspendedResourcesHolder对象中)
  3. 将SuspendedResourcesHolder放入status中,方便后期子事务完成后,恢复外层事务

最后给大家分享一个Github仓库,上面有大彬整理的300多本经典的计算机书籍PDF,包括C语言、C++、Java、Python、前端、数据库、操作系统、计算机网络、数据结构和算法、机器学习、编程人生等,可以star一下,下次找书直接在上面搜索,仓库持续更新中~

Github地址

如果访问不了Github,可以访问码云地址。

码云地址

相关文章:

Spring源码解析——事务增强器

正文 上一篇文章我们讲解了事务的Advisor是如何注册进Spring容器的&#xff0c;也讲解了Spring是如何将有配置事务的类配置上事务的&#xff0c;实际上也就是用了AOP那一套&#xff0c;也讲解了Advisor&#xff0c;pointcut验证流程&#xff0c;至此&#xff0c;事务的初始化工…...

JAVA发送消息到RabbitMq

项目中&#xff0c;作为生产者自定义消息发送到RabbitMq。 1.引入rmq依赖 <!-- rabbitmq 依赖 --><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.9.0</version></dependen…...

Python 函数(lambda 匿名函数、自定义函数、装饰器)基本使用指南

Python 函数 函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码段 lambda 匿名函数 对于单行函数&#xff0c;使用 lambda 表达式可以省去定义函数的过程&#xff0c;让代码更加简洁&#xff1b; 对于不需要多次复用的函数&a…...

第五届芜湖机器人展,正运动助力智能装备“更快更准”更智能!

■展会名称&#xff1a; 第十一届中国(芜湖)科普产品博览交易会-第五届机器人展 ■展会日期 2023年10月21日-23日 ■展馆地点 中国ㆍ芜湖宜居国际博览中心B馆 ■展位号 B029 正运动技术&#xff0c;作为国内领先的运动控制企业&#xff0c;将于2023年10月21日参加芜湖机…...

JVM八股文

1.JVM的内存结构&#xff1f; 2.OOM是什么&#xff0c;怎么排查&#xff1f; 3.请解释四种引用是什么意思有什么区别&#xff1f; 4.GC的回收算法有哪些&#xff1f; 5.怎么判断对象是否存活&#xff1f; 1.什么是JVM内存结构 jvm将虚拟机分为5大区域&#xff0c;程序计数器、…...

代码随想录算法训练营第二十四天丨 回溯算法part02

216.组合总和III 思路 本题就是在 [1,2,3,4,5,6,7,8,9] 这个集合中找到和为n的k个数的组合。 相对于77. 组合 (opens new window)&#xff0c;无非就是多了一个限制&#xff0c;本题是要找到和为n的k个数的组合&#xff0c;而整个集合已经是固定的了[1,...,9]。 本题k相当于…...

【Python机器学习】零基础掌握AgglomerativeClustering聚类

如何解决城市规划问题? 城市规划者们面临一个复杂问题:如何合理地规划土地,使商业、居民、公园和其他设施互相便利,同时又不互相干扰?解决这个问题不仅需要对土地进行精准的分类,还要考虑到土地之间的相互关系。 借助层次聚类算法(Agglomerative Clustering),规划者…...

uniapp小程序中给web-view页面添加授权弹窗(使用cover-view组件覆盖实现该功能)

效果图&#xff1a; web-view是承载网页的容器。会自动铺满整个小程序页面&#xff0c;个人类型的小程序暂不支持使用。 再看下面一个提示&#xff1a; 每个页面只能有一个 web-view&#xff0c;web-view 会自动铺满整个页面&#xff0c;并覆盖其他组件。 也就是说&#xff0c;…...

2023年全球及中国CGT CDMO市场发展现状分析:CGT 渗透率有效助力CGT CDMO快速发展[图]

与传统药物相比&#xff0c;CGT的外包服务更注重活体开发过程&#xff0c;如质粒、病毒、细胞的生产及纯化。标准化、规模化的工艺流程对最终制备的产品起到重要影响&#xff0c;是获取及制备能够满足临床需求的高质量CGT产品的关键。 CGT CDMO服务内容 资料来源&#xff1a;共…...

上抖音热搜榜需要做哪些准备?

要想在抖音上获得高曝光&#xff0c;首先需要了解抖音热搜榜的算法和规则。抖音热搜榜的排名主要取决于作品的点赞数、评论数、分享数和播放量。其中&#xff0c;播放量是影响排名的关键因素。因此&#xff0c;在创作作品时&#xff0c;要注重提高作品的播放量。此外&#xff0…...

LDA代码训练报错记录

1、AttributeError: ‘CountVectorizer‘ object has no attribute ‘get_feature_names‘ 代码内容&#xff1a; tf_feature_names tf_vectorizer.get_feature_names()报错信息 AttributeError: CountVectorizer object has no attribute get_feature_names报错解析&#…...

【吞噬星空】爽翻,徐欣喜提永恒之体,罗峰秒杀败类,阿特金磕头认错

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析国漫资讯。 吞噬星空动画第89集终于更新了&#xff0c;阿特金三大巨头的好日子到头了&#xff0c;从他们对徐欣出手的那一刻&#xff0c;就已经有取死之道。如今罗峰强势回归&#xff0c;上演复仇戏码&#xff0c;让大家看…...

【c++】跟webrtc学状态改变

peerconn的状态看起来只是为了通知上层PeerConnectionState // See https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectionstateenum class PeerConnectionState {kNew,kConnecting,kConnected,kDisconnected,kFailed,kClosed,};static constexpr absl...

【入门】.Net Core 6 WebApi 项目搭建

一、创建项目 1.1.创建新项目&#xff1a;打开开发工具>创建新项目>搜索API>选择C#语言的ASP.NET Core Web API 1.2.配置新项目&#xff1a;**自定义项目信息以及存储路径 1.3.其他信息&#xff1a;这里框架必须选择.NET 6.0,其他配置默认勾选即可&#xff0c;也可以根…...

xtrabackup备份 脚本

1、全量备份在周末晚上22点执行备份&#xff0c;增量是周一到周六晚上22点执行 2、考虑到增量备份第一次是根据全量备份开始备份&#xff0c;后面都是根据上一次增量备份在增量脚本做了if判断&#xff0c;周日做一次目录清理 3、每周日晚上91点50清理目录 22点就在次备份&#…...

13SpringMVC中拦截器的配置(拦截规则)和多个拦截器的preHandle,postHandle执行顺序原理详解

拦截器 Servlet中的过滤器的实现及其原理,参考文章 配置一个拦截器 SpringMVC中请求的处理流程: 用户请求—>listener—>filter—>DispatcherServlet—>filter—>preHandle—>controller—>postHandle 第一步: 编写一个Java类实现HandlerInterceptor(…...

Liunx中系统安全及文件系统(极其粗糙版)

PS&#xff1a;下面知识点还很粗糙下次有时间再改 系统安全&#xff1a; 系统安全和数据防护&#xff0c;数据备份的资质 比如三台服务器&#xff1a; 500万 工信部是有要求的&#xff0c;组织必须保证处理的个人数据的安全性 品牌形象如何维护呢 基于liunx的安全加固措施…...

Java中的数组

前言&#xff1a; 本篇博客将为大家介绍Java中的数组的相关知识。 目录 基本介绍 概念相关 数组的使用 数组是引用类型 应用场景 保存数据 作为方法的参数 作为方法的返回值 练习 数组转字符串 数组拷贝 求数组中元素的平均值 查找数组中的指定元素&#xff08;二…...

Java反射调用jar包实现多态

上一篇实现了反射调用jar包&#xff0c;但是没有实现多态&#xff0c;这次先给自己的jar包类抽象一个接口&#xff0c;然后实现类实现接口。最后调用放反射得到的对像转换成接口类型调用执行。 定义接口&#xff0c;指定包为ZLZJar package ZLZJar;public interface ITest {p…...

PowerBI 一些基础功能

1、PowerBI创建日期表 1.1、Power BI 日期表 - 知乎日期是做数据分析的时候使用最频繁的分析维度&#xff0c;一般建议建立单独的日期维度表&#xff0c;并与事实表的日期字段建立连接。 建立日期维度表可通过DAX函数的方式进行&#xff1a; 日期表 CALENDAR(DATE("2023&…...

Mac用命令行安装Adobe代码字体Source Code Pro

执行命令 brew tap homebrew/cask-fonts && brew cask install font-source-code-pro...

RustDay05------Exercise[31-40]

31.结构体申明 结构体在这里给了三种声明样式 (1)字典样式的键值对(使用花括号) (2)元组样式的数值元组(使用圆括号) (3)空结构体,可以被格式化输出名字 // structs1.rs // Address all the TODOs to make the tests pass! // Execute rustlings hint structs1 or use the…...

wireshark过滤器的简单介绍

wireshark过滤器的简单介绍 Wireshark的过滤器主要分为捕获过滤器和显示过滤器两种&#xff0c;其中捕获过滤器在数据包捕获时起作用&#xff0c;而显示过滤器用于在已捕获的数据包的集合中筛选数据。以下是一些Wireshark过滤器的详细介绍&#xff1a; 捕获过滤器&#xff1a;…...

数据结构:二叉树(1)

目录 树的概念 树的表示形式 二叉树 二叉树的性质 题目 二叉树的存储 链式存储 初始化二叉树 二叉树的遍历 前序遍历&#xff1a;根&#x1f449;左子树&#x1f449;右子树 中序遍历&#xff1a;左子树&#x1f449;根&#x1f449;右子树 后序遍历&#xff1a;左子…...

[nlp] chathome—家居装修垂类大语言模型的开发和评估

ChatHome: Development and Evaluation of a Domain-Specific LanguageModel for Home Renovation ChatHome: 家居装修垂类大语言模型的开发和评估 1、摘要: 我们的方法包括两个步骤:首先,使用广泛的家庭装修数据集(包括专业文章、标准文档和网络内容)对通用模型进行后预训…...

http(下)

http的工作流程&#xff1a; 客户端---服务端通信过程 请求----响应的模型 建立连接&#xff1a;tcp/ip协议与服务器建立连接&#xff08;三次握手&#xff09;&#xff0c;客户端向服务器的80端口发送连接请求 发送请求&#xff1a;一旦连接建立之后&#xff0c;客户端就像…...

Python学习基础笔记七十二——IDE集成开发环境

集成开发环境&#xff0c;英文缩写是IDE。 IDE可以帮你更高效地开发项目代码。因为它提供了非常实用的功能&#xff0c;比如项目文件管理、语法高亮、代码导航、自动补齐代码、语法静态检查、调试、版本控制等等。 两款IDE&#xff1a;Pycharm和VSCode。 pycharm中的代码文件都…...

[MQ]Win平台RocketMQ安装启动

1、下载 官网下载地址&#xff1a;https://rocketmq.apache.org/zh/download 2、解压ZIP包 解压rocketmq-all-x.x.x-bin-release.zip到目录。 比如我解压到了E:\Env\MQ_rocket\rocketmq-all-5.1.4-bin-release 3、配置环境变量 ROCKETMQ_HOME 4、RocketMQ JVM内存配置 这个需要…...

vscode工程屏蔽不使用的文件夹或文件的方法

一. 简介 vscode是一款 微软提供的免费的代码编辑软件。 对于 IMX6ULL-ALPHA开发板而言&#xff0c;NXP官方uboot一定会支持不止 IMX6ULL芯片的代码&#xff0c;也不止支持 一种架构&#xff0c;还支持其他芯片或架构的源码文件。 为了方便阅读代码&#xff0c;vscode软件可…...

黑马JVM总结(三十四)

&#xff08;1&#xff09;JMM概述 &#xff08;2&#xff09;JMM-原子性-synchronized java内存模型是如何保证原子性的呢&#xff0c;它是通过synchroized关键字&#xff0c;来达到这个目的的 第一个线程来了进入同步代码块之后&#xff0c;把这个对象加上锁了&#xff0c;…...

怎么做一元购物网站/真正免费建站

上篇 SpringBootMongoDB实现一物流订单系统(上) 本文收录在公众号&#xff1a;bigsai第三步 订单更新(追加订单)创建完订单之后&#xff0c;无数配送公司和人员便开始配送物流&#xff0c;而我们在查询的时候&#xff0c;物流状态信息也能够时不时的刷新&#xff0c;具体物流信…...

phpcms仿行业网站/全网推广公司

一、volatile的定义 Java编程语言允许线程访问共享变量&#xff0c;为了确保共享变量能被准确和一致地更新&#xff0c;线程应该确保通过排他锁单独获得这个变量。Java语言 提供了volatile&#xff0c;在某些情况下比锁要更加方便。如果一个字段被声明成volatile&#xff0c;Ja…...

web开发是做网站/宁德市人社局官网

说明本文假定读者已具备基本的C编译知识。如非特殊说明&#xff0c;文中“源文件”指 * .c文件&#xff0c;“头文件”指 *.h文件&#xff0c;“引用”指包含头文件。一、头文件作用C语言里&#xff0c;每个源文件是一个模块&#xff0c;头文件为使用该模块的用户提供接口。接口…...

商丘柘城做网站/北京培训学校

在Eclipse上创建Web项目&#xff0c;默认会产生一个WebRootWEB-INFlib目录&#xff0c;jar包复制到该目录后会自动加载到Web App Libraries库中&#xff0c;效果如下&#xff1a;而如果创建普通的Java项目&#xff0c;一般需要自己创建一个lib目录&#xff0c;再将jar包复制到该…...

日照有做渔家网站的吗/重庆seo点击工具

从输入URL地址到显示完整的页面Webkit都做了哪些事情 从输入地址到获取到数据的流程 1、输入URL地址,如&#xff1a;http://www.yejm16361.com/demo...。 2、DNS解析URL地址中的域名返回IP地址&#xff08;如果是主机名是IP地址就跳过该步骤&#xff09;。 3、 建立TCP连接&…...

好网站推荐一下/品牌推广内容

微软等数据结构算法面试100题系列之网友精彩回复 [一] ------------------------------ 作者:July 飞雪 一直不断有网友来信&#xff0c;想要微软等100题的答案&#xff0c;可由于整理这100题的答案&#xff0c;分量太大。 所以&#xff0c;后60题的答案&#xff0c;一直迟迟…...