• Spring Trasnaction管理(3)- 事务嵌套


    问题导读

    • Spring 如何管理嵌套的事务
    • Spring事务传播机制 Nested 和 RequireNew 有何区别

    事务传播机制

    事务的传播机制应该都比较熟悉
    在日常开发中会遇到需要事务嵌套的情况
    Spring在进行事务管理进入新的事务传播机制时,如果检查到当前线程已经存在线程会从getTransaction方法中调用
    AbstractPlatformTransactionManager.handleExistingTransaction

    private TransactionStatus handleExistingTransaction(
    			TransactionDefinition definition, Object transaction, boolean debugEnabled)
    			throws TransactionException {
                    //如果新事务的传播机制是Never就抛出异常
    		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
    			throw new IllegalTransactionStateException(
    					"Existing transaction found for transaction marked with propagation 'never'");
    		}
                    //如果新事务的传播机制是NotSupported就挂起当前线程
    		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
    			if (debugEnabled) {
    				logger.debug("Suspending current transaction");
    			}
                            //挂起事务
    			Object suspendedResources = suspend(transaction);
    			boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
                            //prepareTransactionStatus最后的一个参数是挂起的信息,用于结束后恢复
    			return prepareTransactionStatus(
    					definition, null, false, newSynchronization, debugEnabled, suspendedResources);
    		}
                    //如果新事务的传播机制是RequireNew就挂起当前线程,并另起事务
    		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 {
    				boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    				DefaultTransactionStatus status = newTransactionStatus(
    						definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
                                    //开始新事物
    				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() + "]");
    			}
                            //判断是否支持Savepoint
    			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);
                                    //创建savepoint
    				status.createAndHoldSavepoint();
    				return status;
    			}
    			else {
    				//如果不支持(一般为JTA事务),行为类似RequireNew
    				// 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.
    				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)就沿用当前线程
    		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);
    	}
    

    这个逻辑是定义在AbstractPlatformTransactionManager,是默认TransactionManager的父类,具体的事务挂起、开始等操作都是子类的方法来实现的

    事务的挂起

    事务是如何挂起的呢,还是拿DataSourceTransactionManager来看

            //Suspend这里主要是将TransactionSynchronizationManager内部记录的Transaction信息注销,之后handleExistingTransaction会根据传播机制来控制是否新建连接、事务
    	protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException {
    		...
    		Object suspendedResources = null;
    		if (transaction != null) {
    			suspendedResources = doSuspend(transaction);
    		}
    		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);
    		return new SuspendedResourcesHolder(
    				suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
    		...
    	}
    
    	protected Object doSuspend(Object transaction) {
    		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    		txObject.setConnectionHolder(null);
                    //将数据库连接从TransactionSynchronizationManager中注销
    		ConnectionHolder conHolder = (ConnectionHolder)
    				TransactionSynchronizationManager.unbindResource(this.dataSource);
    		return conHolder;
    	}
            //在恢复时,会将suspendedResources中的连接重新注册到TransactionSynchronizationManager
    	protected void doResume(Object transaction, Object suspendedResources) {
    		ConnectionHolder conHolder = (ConnectionHolder) suspendedResources;
    		TransactionSynchronizationManager.bindResource(this.dataSource, conHolder);
    	}
    

    小结

    在handleExistingTransaction中,Spring规定了事务传播机制的行为,
    其中RequiredNew是挂起当前连接,重新创建连接,这样来保证事务之间不会互相影响,但是在使用时要注意如果这两个事务操作的是同一条记录的话,可能会导致死锁的情况,因为他们是两个session。
    而Nested是利用了JDBC3.0的savepoint来实现的,这样能很好的利用同一个连接来完成操作,避免可能发生的死锁。

  • 相关阅读:
    洛谷-P1427 小鱼的数字游戏
    洛谷-P1047 校门外的树
    洛谷-P1046 陶陶摘苹果
    洛谷-P1980 计数问题
    洛谷-P1424 小鱼的航程(改进版)
    洛谷-P1423 小玉在游泳
    洛谷-P1035 级数求和
    洛谷-P1008 三连击
    Oracle 11g r2 rac +openfiler 2.99 安装
    26 主备库并行复制策略
  • 原文地址:https://www.cnblogs.com/resentment/p/5753013.html
Copyright © 2020-2023  润新知