Spring事务(三)——传播属性之REQUIRED

        事务是与连接对象紧密相关的,事务属性用来控制事务流转。 

        Spring事务的传播属性有以下几种:

  • Propagation.REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,则加入到这个事务中。默认属性,也是最常使用。
  • Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与Propagation.REQUIRED类似的操作。
  • Propagation.REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  • Propagation.SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  • Propagation.MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
  • Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

        前面三种使用居多。

        本篇看REQUIRED传播属性。

        通过案例代码进入本篇内容,有如下事务方法: 

    @Transactional
    @Override
    public void transation(ConsultConfigArea area, ZgGoods zgGoods) {
        areaService.addArea(area);
        goodsService.addGoods(zgGoods);
    }

         transation()方法事务执行的大致流程就是下面,为了便于理解,用伪代码来表示:

        // transation()方法执行
        // 开启事务
        createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
        try {

            // 执行被代理方法
            invocation.proceedWithInvocation();
            // 相当于执行下面方法:
            @Transactional
            public void transation(ConsultConfigArea area, ZgGoods zgGoods) throws JamesException {
                areaService.addArea(area);
                goodsService.addGoods(zgGoods);
            }

        } catch (Exception e) {
            // 事务回滚
            completeTransactionAfterThrowing(txInfo, ex);
        }
        // 事务提交
        commitTransactionAfterReturning(txInfo);
        // transation()方法结束

         调用的业务方法同样也有@Transactional注解:

    @Transactional
    @Override
    public int addArea(ConsultConfigArea area) {
        int i = commonMapper.addArea(area);
        return i;
    }
    @Transactional
    @Override
    public void addGoods(ZgGoods zgGoods) throws JamesException {
        commonMapper.addGood(zgGoods);
    }

        那么,伪代码就会变成这样:

        // transation()方法执行
        // 开启事务
        createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
        try {

            // 执行被代理方法
            invocation.proceedWithInvocation();
            // 相当于执行下面方法:

            // 1.areaService.addArea(area)开始执行
            // 开启事务
            createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
            try {

                // 执行被代理方法
                invocation.proceedWithInvocation();
                // 相当于执行下面方法:
                commonMapper.addArea(area);

            } catch (Exception e) {
                // 事务回滚
                completeTransactionAfterThrowing(txInfo, ex);
                throw new Exception(e);
            }
            // 事务提交
            commitTransactionAfterReturning(txInfo);
            // areaService.addArea(area)执行结束

         ————————————————————————————————————————————————————————————

            // 2.goodsService.addGoods(zgGoods)开始执行
            // 开启事务
            createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
            try {

                // 执行被代理方法
                invocation.proceedWithInvocation();
                // 相当于执行下面方法:
                commonMapper.addGood(zgGoods);

            } catch (Exception e) {
                // 事务回滚
                completeTransactionAfterThrowing(txInfo, ex);
                throw new Exception(e);
            }
            // 事务提交
            commitTransactionAfterReturning(txInfo);
            // goodsService.addGoods(zgGoods)执行结束

        } catch (Exception e) {
            // 事务回滚
            completeTransactionAfterThrowing(txInfo, ex);
        }
        // 事务提交
        commitTransactionAfterReturning(txInfo);
        // transation()方法结束

        如果是PROPAGATION_REQUIRED属性,示例代理中的三个方法使用的是同一个事务。

    @Autowired
    private DataSource dataSource;

    ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);

        这样,就可以拿到连接对象。上面代码执行得到的连接对象为同一个:

扫描二维码关注公众号,回复: 14653250 查看本文章

         分析一下执行过程,三个方法transation(ConsultConfigArea area, ZgGoods zgGoods)、       areaService.addArea(area)、goodsService.addGoods(zgGoods)是通过不同的代理对象去执行,那么TransactionInterceptor#invoke()会执行三次,在AbstractPlatformTransactionManager#getTransaction()方法:

	public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException {

		// Use defaults if no transaction definition given.
		TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

		// 得到一个DataSourcetranctionObject类(就是事务对象)****
		Object transaction = doGetTransaction();

		// 判断之前是否已存在事务对象(第一次进来为空)
		if (isExistingTransaction(transaction)) {
			// Existing transaction found -> check propagation behavior to find out how to behave.
			// 如果不是第一次进来, 则走这里,表示之前已开启事务,则为现有事务创建TransactionStatus。
			return handleExistingTransaction(def, transaction, debugEnabled);
		}

      ----省略无关代码---- 

        获取连接对象doGetTransaction():

        所属类org.springframework.jdbc.datasource.DataSourceTransactionManager

	protected Object doGetTransaction() {
		// 创建一个事务对象
		DataSourceTransactionObject txObject = new DataSourceTransactionObject();
		// 是否创建回滚点,默认为true
		txObject.setSavepointAllowed(isNestedTransactionAllowed());
		// 根据DataSource对象去ThreadLocal中拿连接对象ConnectionHolder,第一次进来拿的时候是空的
		ConnectionHolder conHolder =
				(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
		txObject.setConnectionHolder(conHolder, false);
		return txObject;
	}

        Spring获取连接对象就是从ThreadLocal中获取的,第一次进来为空,那么就会进入后面的逻辑, 在startTransaction()中的doBegin()方法中创建连接对象:

        所属类:org.springframework.jdbc.datasource.DataSourceTransactionManager

	protected void doBegin(Object transaction, TransactionDefinition definition) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		Connection con = null;

		try {
			// 检查事务中是否有连接对象(DataSource)
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
				// 没有的话通过数据源管理器拿DataSource对象,再从DataSource中获取连接对象
				Connection newCon = obtainDataSource().getConnection();
				if (logger.isDebugEnabled()) {
					logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
				}
				// 把连接对象包装成一个ConnectionHolder,并设置到事务对象(txObject)中
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			}
			// 设置有事务的标识
			txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
			con = txObject.getConnectionHolder().getConnection();

			// 根据@Transactional注解中的配置的隔离级别,设置Connection的readonly与隔离级别
			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");
				}
				// 关闭自动提交,提交由Spring控制
				con.setAutoCommit(false);
			}
			// 判断事务为只读事务
			prepareTransactionalConnection(con, definition);
			// 设置事务状态(是否为活跃的)为true
			txObject.getConnectionHolder().setTransactionActive(true);

			// 设置事务(数据库连接)超时时间
			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			}

			// Bind the connection holder to the thread.
			// 把连接对象和当前线程进行绑定,把数据源和连接的绑定关系设置到ThreadLocal<Map<数据源,连接对象>>,为threadId和Map的映射关系
			// 后面就可以通过threadId拿到Map,进而通过相同的数据源对象拿到数据库连接
			if (txObject.isNewConnectionHolder()) {
				TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
			}
		}

       把连接对象包装成ConnectionHolder,赋值给DataSourceTransactionObject事务对象,然后关闭自动提交,最后数据源对象和连接对象建立绑定关系,放到ThreadLocal中:

        所属类:org.springframework.transaction.support.TransactionSynchronizationManager

	public static void bindResource(Object key, Object value) throws IllegalStateException {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Assert.notNull(value, "Value must not be null");
		Map<Object, Object> map = resources.get();
		// set ThreadLocal Map if none found
		if (map == null) {
			map = new HashMap<>();
			resources.set(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() + "]");
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
					Thread.currentThread().getName() + "]");
		}
	}

        这就是绑定过程,resources就是ThreadLocal:

	// key为DataSource对象,value为ConnectionHolder对象
	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

        上面实例代码会执行三次事务的方法,总结一下:

        第一次:执行外层transation() 方法:

AbstractPlatformTransactionManager.getTransaction()——>

DataSourceTransactionManager.doGetTransaction()返回null(ThreadLocal中没有)——>

AbstractPlatformTransactionManager.startTransaction()中的doBegin()方法从DataSource获取连接对象,包装成ConnectionHolder(newTransaction属性为true),放到事务对象DataSourceTransactionObject中,建立绑定关系,放到ThreadLocal中;

        第二次:执行addArea()方法:

AbstractPlatformTransactionManager.getTransaction()——>

DataSourceTransactionManager.doGetTransaction()通过ThreadId拿到连接对象,但newTransaction属性为false(因为是之前的连接对象,不是新建的)——>

存在事务对象进入AbstractPlatformTransactionManager.handleExistingTransaction()执行最后一行,创建DefaultTransactionStatus事务状态对象(newTransaction属性为false)——>

执行被代理方法addArea()——>

TransactionAspectSupport.commitTransactionAfterReturning()事务提交——>

AbstractPlatformTransactionManager.processCommit()通过status.isNewTransaction()判断是否为新事务,是则提交,此时为false,所以不提交。

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
      ----省略无关代码---- 
			try {
				
				// 如果是新事务(前面提交的newTransaction属性),则提交
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction commit");
					}
					unexpectedRollback = status.isGlobalRollbackOnly();
					// 事务提交
					doCommit(status);
				}
      ----省略无关代码---- 

        第三次:执行addGoods()方法:

        流程和第二次进来一样,也没有提交事务;

        事务提交是最外层的transation() 方法执行完被代理方法之后进行,此时事务状态TransactionStatus的newTransaction属性为true:

        接着进入AbstractPlatformTransactionManager.processCommit(),进行事务提交:

        所属类:org.springframework.jdbc.datasource.DataSourceTransactionManager

	protected void doCommit(DefaultTransactionStatus status) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
		Connection con = txObject.getConnectionHolder().getConnection();
		if (status.isDebug()) {
			logger.debug("Committing JDBC transaction on Connection [" + con + "]");
		}
		try {
			// 调用JDK的事务提交方法
			con.commit();
		}
		catch (SQLException ex) {
			throw new TransactionSystemException("Could not commit JDBC transaction", ex);
		}
	}

        异常情况处理:

        1. 前面执行的addArea()方法出现异常:

    @Transactional()
    public int addArea(ConsultConfigArea area) {
        ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        System.out.println(resource.getConnection());
        int i = commonMapper.addArea(area);
        if (true) throw new RuntimeException("ssf");
        return i;
    }

        那么就会被TransactionAspectSupport.invokeWithinTransaction()的try/catch捕获,并将异常向上抛出;后面的addGoods()方法就不再执行,抛出的异常被最外层的transation() 方法捕获,执行回滚操作,所以数据库的两张表都没有数据。

        2. 后面执行的addGoods()方法出现异常:

    @Transactional
    public void addGoods(ZgGoods zgGoods) {
        ConnectionHolder resource = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        System.out.println(resource.getConnection());
        int i = commonMapper.addGood(zgGoods);
        if (true) throw new RuntimeException("ssf");
    }

        虽然前面的addArea()执行成功,但事务状态newTransaction属性为false,不会提交事务;addGoods()出现异常,被外层的transation() 方法捕获,执行回滚操作,所以数据库的两张表也没有数据。

       3. 内部方法addGoods()出现异常,外层transation() try/catch异常,未向上抛:

    @Transactional
    @Override
    public void transation(ConsultConfigArea area, ZgGoods zgGoods) throws JamesException {
        try {
            areaService.addArea(area);
            goodsService.addGoods(zgGoods);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

        这种情况,addArea()执行成功,但事务状态newTransaction属性为false,不会提交事务;addGoods()出现异常,不会执行commitTransactionAfterReturning()事务提交;

        异常被自己的try/catch捕获,但未做任何处理;

        继续执行外层的transation() 方法的commitTransactionAfterReturning()事务提交,进入commit()方法,在这里执行了回滚操作:

        所属类:org.springframework.transaction.support.AbstractPlatformTransactionManager

        commit()重执行回滚操作???数据库没有插入数据!和我们的预期不一样!什么鬼?

        因为addGoods()方法出现异常,会执行completeTransactionAfterThrowing()回滚操作:

        所属类:org.springframework.transaction.interceptor.TransactionAspectSupport

	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);
			}

			// transactionAttribute的实现类为RuledBasedTransactionAttribute,父类为DefaultTransactionAttribute
			// 把异常传进来,执行RuledBasedTransactionAttribute.rollbackOn()方法
			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;
				}
			}
		}
	}

        进入rollback(TransactionStatus status) 方法:

        所属类:org.springframework.transaction.support.AbstractPlatformTransactionManager

	public final void rollback(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;
		// 回滚方法
		processRollback(defStatus, false);
	}
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
		try {
			boolean unexpectedRollback = unexpected;

			try {
				triggerBeforeCompletion(status);
				// 是否存在回滚点,如mysql的savepoint
				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Rolling back transaction to savepoint");
					}
					// 回滚到上一个savepoint位置
					status.rollbackToHeldSavepoint();
				}
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction rollback");
					}
					// 如果当前执行的方法是新开了一个事务,则直接回滚
					doRollback(status);
				}
				else {
					// Participating in larger transaction
					// 如果当前执行的方法,共用了之前已存在的事务,而当前方法抛出异常,则判断整个事务是否要回滚
					if (status.hasTransaction()) {
						// 如果一个事务有两个方法,第二个抛异常了,则第二个方法执行失败进行回滚
						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
							}
							// 直接将rollbackOnly设置到ConnectionHolder中取,表示整个事务都要回滚
							doSetRollbackOnly(status);
						}
						else {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
							}
						}
					}
					else {
						logger.debug("Should roll back transaction but cannot - no transaction available");
					}
					// Unexpected rollback only matters here if we're asked to fail early
					if (!isFailEarlyOnGlobalRollbackOnly()) {
						unexpectedRollback = false;
					}
				}
			}
			catch (RuntimeException | Error ex) {
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
				throw ex;
			}

			triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

			// Raise UnexpectedRollbackException if we had a global rollback-only marker
			if (unexpectedRollback) {
				throw new UnexpectedRollbackException(
						"Transaction rolled back because it has been marked as rollback-only");
			}
		}
		finally {
			cleanupAfterCompletion(status);
		}
	}

        在doSetRollbackOnly(DefaultTransactionStatus status)设置事务对象的setRollbackOnly()属性为true。 

         这里需要注意的是, 如果当前执行的方法,已存在事务,而当前方法抛出异常,则判断整个事务是否要回滚,如果一个事务有两个方法,第二个抛异常了,则第二个方法执行失败进行回滚,会将rollbackOnly设置到ConnectionHolder中,表示整个事务都要回滚,看doSetRollbackOnly(status)方法:
        所属类:org.springframework.jdbc.datasource.DataSourceTransactionManager

	protected void doSetRollbackOnly(DefaultTransactionStatus status) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
		if (status.isDebug()) {
			logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() +
					"] rollback-only");
		}
		txObject.setRollbackOnly();
	}

        进入本类的setRollbackOnly()方法:

		public void setRollbackOnly() {
			getConnectionHolder().setRollbackOnly();
		}

         拿到连接对象ConnectionHolder,并设置rollbackOnly属性为true,所以虽然是事务提交,但是前面将TransactionStatus的rollbackOnly属性设置为true,所以在这里进行了回滚操作:

public final void commit(TransactionStatus status) throws TransactionException {
      ----省略无关代码---- 
		// 判断此事务在此之前是否设置了回滚,并且isGlobalRollbackOnly()为true
		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);
	}

        所以外层的transation() 方法不论是否将异常向上抛,都进行了回滚,导致数据库没数据。

        4. 内部方法addGoods()方法try/catch异常,未向上抛:

    @Transactional
    public void addGoods(ZgGoods zgGoods) {
        try {
            commonMapper.addGood(zgGoods);
            if (true) throw new RuntimeException("ssf");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

        此时数据库却有数据。因为虽然有异常,但是却吞掉了,没有抛出,所以外层的transation() 方法未捕捉到异常,执行正常的事务提交了。

        总结:内部方法出现异常被吞掉,不会回滚;外部方法异常抛出或吞掉,都会回滚。

猜你喜欢

转载自blog.csdn.net/qq_40213907/article/details/121189553
今日推荐