被@Transactional注解的方法如何使用异步调用

文章讲述了在Spring中,使用@Transactional注解的方法在进行异步调用时可能会遇到事务还未提交的问题。通过TransactionSynchronizationManager注册事务同步器,可以确保在事务提交后执行异步任务,避免数据不一致。TransactionSynchronizationManager利用ThreadLocal保存事务同步器,确保在事务提交后回调已注册的方法。
摘要由CSDN通过智能技术生成

@Transactional注解的方法如何使用异步调用

被@Transactional注解后的方法由于是统一管理事务,需要等到方法结束后事务才会被提交,那如果想在业务数据入库后,异步调用比如发送mq、发送邮件、短信等情况下,可能会存在事务还没提交异步方法先执行的情况,比如我们有可能写如下的业务代码:

    @PostMapping(value = "/transactional", produces = MediaType.APPLICATION_JSON_VALUE)
    @Transactional(rollbackFor = Exception.class)
    public ResponseEntity<?> transactional(@RequestBody TestModel testModel) {
        logger.info(testModel.toString());
        //做一系列的入库操作
        UserRegistry registry = new UserRegistry();
        userDao.save(registry);
        List<UserRegistry> all = userDao.findAll();
        logger.info("总数据条数:{}", all.size());
        //借助线程池执行异步任务
        taskExecutor.execute(() -> {
            //TODO 这里执行异步操作,并查询出上面入库操作的一些结果
        });
        return ResponseEntity.ok("ok");
    }

当线程池的任务执行的时候可能事务还没提交,这样就查不到一些入库的数据。可以把上面代码做如下改动即可解决问题:

    @PostMapping(value = "/transactional", produces = MediaType.APPLICATION_JSON_VALUE)
    @Transactional(rollbackFor = Exception.class)
    public ResponseEntity<?> transactional(@RequestBody TestModel testModel) {
        logger.info(testModel.toString());
        //做一系列的入库操作
        UserRegistry registry = new UserRegistry();
        userDao.save(registry);
        List<UserRegistry> all = userDao.findAll();
        logger.info("总数据条数:{}", all.size());
        //给当前线程注册一个事务同步器
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCompletion(int status) {
                //保证事务一定是已经提交了
                if (TransactionSynchronization.STATUS_COMMITTED == status) {
                    //借助线程池执行异步任务
                    taskExecutor.execute(() -> {
                        //TODO 这里执行异步操作,并查询出上面入库操作的一些结果
                    });
                }
            }
        });
        return ResponseEntity.ok("ok");
    }

TransactionSynchronizationManager是底层是如何实现的

被@Transactional注解的方法,spring容器在在启动的时候会默认给该类生成一个代理对象,并且在调用方法的时候会实际交给TransactionInterceptor这个类来处理,看一下TransactionInterceptor这个类里面调用实际方法的地方:

	public Object invoke(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, new CoroutinesInvocationCallback() {
			@Override
			@Nullable
			public Object proceedWithInvocation() throws Throwable {
				//调用实际业务方法的地方
				return invocation.proceed();
			}
			@Override
			public Object getTarget() {
				return invocation.getThis();
			}
			@Override
			public Object[] getArguments() {
				return invocation.getArguments();
			}
		});
	}

上面invoke方法里面调用了invokeWithinTransaction方法,该方法有3个入参:目标方法、目标类、接口回调;前两个参数不用多说,第三个参数里面实现了proceedWithInvocation方法,方法里面的invocation.proceed()就是调用业务方法的地方,那么在调用方法之前做了什么处理,看一下invokeWithinTransaction方法这个方法在TransactionAspectSupport这个类里面,看一下这个方法的部分代码:

		if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
			//这里开启事务
			TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				// This is an around advice: Invoke the next interceptor in the chain.
				// This will normally result in a target object being invoked.
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// target invocation exception
				//异常之后回滚事务
				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;
		}

上面的代码主要是开启事务和提交事务的,主要看一下commitTransactionAfterReturning方法里面都干了什么

	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方法

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

继续看processCommit方法,这个方法里面做了事务提交前、提交、提交后的处理

	private void processCommit(DefaultTransactionStatus status) throws TransactionException {
		try {
			boolean beforeCompletionInvoked = false;

			try {
				boolean unexpectedRollback = false;
				prepareForCommit(status);
				triggerBeforeCommit(status);
				triggerBeforeCompletion(status);
				beforeCompletionInvoked = true;

				if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Releasing transaction savepoint");
					}
					unexpectedRollback = status.isGlobalRollbackOnly();
					status.releaseHeldSavepoint();
				}
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction commit");
					}
					unexpectedRollback = status.isGlobalRollbackOnly();
					doCommit(status);
				}
				else if (isFailEarlyOnGlobalRollbackOnly()) {
					unexpectedRollback = status.isGlobalRollbackOnly();
				}

				// Throw UnexpectedRollbackException if we have a global rollback-only
				// marker but still didn't get a corresponding exception from commit.
				if (unexpectedRollback) {
					throw new UnexpectedRollbackException(
							"Transaction silently rolled back because it has been marked as rollback-only");
				}
			}
			catch (UnexpectedRollbackException ex) {
				// can only be caused by doCommit
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
				throw ex;
			}
			catch (TransactionException ex) {
				// can only be caused by doCommit
				if (isRollbackOnCommitFailure()) {
					doRollbackOnCommitException(status, ex);
				}
				else {
					triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
				}
				throw ex;
			}
			catch (RuntimeException | Error ex) {
				if (!beforeCompletionInvoked) {
					triggerBeforeCompletion(status);
				}
				doRollbackOnCommitException(status, ex);
				throw ex;
			}

			// Trigger afterCommit callbacks, with an exception thrown there
			// propagated to callers but the transaction still considered as committed.
			try {
				triggerAfterCommit(status);
			}
			finally {
				//上面的逻辑主要做事务提交前、提交的一些处理
				triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
			}

		}
		finally {
			cleanupAfterCompletion(status);
		}
	}

重点就是上面的triggerAfterCompletion,事务提交后的处理,我们之前给当前线程注册的一个事务同步器,就是在这个方法里面被触发调用了,看一下triggerAfterCompletion的代码

	private void triggerAfterCompletion(DefaultTransactionStatus status, int completionStatus) {
		if (status.isNewSynchronization()) {
			List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
			TransactionSynchronizationManager.clearSynchronization();
			if (!status.hasTransaction() || status.isNewTransaction()) {
				// No transaction or new transaction for the current scope ->
				// invoke the afterCompletion callbacks immediately
				invokeAfterCompletion(synchronizations, completionStatus);
			}
			else if (!synchronizations.isEmpty()) {
				// Existing transaction that we participate in, controlled outside
				// of the scope of this Spring transaction manager -> try to register
				// an afterCompletion callback with the existing (JTA) transaction.
				registerAfterCompletionWithExistingTransaction(status.getTransaction(), synchronizations);
			}
		}
	}

看一下TransactionSynchronizationManager.getSynchronizations()这个里面干了什么

	public static List<TransactionSynchronization> getSynchronizations() throws IllegalStateException {
		Set<TransactionSynchronization> synchs = synchronizations.get();
		if (synchs == null) {
			throw new IllegalStateException("Transaction synchronization is not active");
		}
		// Return unmodifiable snapshot, to avoid ConcurrentModificationExceptions
		// while iterating and invoking synchronization callbacks that in turn
		// might register further synchronizations.
		if (synchs.isEmpty()) {
			return Collections.emptyList();
		}
		else {
			// Sort lazily here, not in registerSynchronization.
			List<TransactionSynchronization> sortedSynchs = new ArrayList<>(synchs);
			OrderComparator.sort(sortedSynchs);
			return Collections.unmodifiableList(sortedSynchs);
		}
	}

这里面的synchronizations.get(),synchronizations变量在TransactionSynchronizationManager这个类里面定义的类型如下,看到下面这个定义大概就明白是为什么了,这里用到了ThreadLocal,所以我们之前给当前线程注册的事务同步器,是能够获取到的

	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");

再继续看triggerAfterCompletion方法,当没有事务或者是一个新的事务的时候会走invokeAfterCompletion方法,看一下这个方法的代码

	protected final void invokeAfterCompletion(List<TransactionSynchronization> synchronizations, int completionStatus) {
		TransactionSynchronizationUtils.invokeAfterCompletion(synchronizations, completionStatus);
	}

跳过上面继续看TransactionSynchronizationUtils类里面的invokeAfterCompletion方法,可以看到会遍历所有被注册进来的TransactionSynchronization然后进行调用

	public static void invokeAfterCompletion(@Nullable List<TransactionSynchronization> synchronizations,
			int completionStatus) {

		if (synchronizations != null) {
			for (TransactionSynchronization synchronization : synchronizations) {
				try {
					//这里就回调了我们之前注册的事务同步器里面的方法
					synchronization.afterCompletion(completionStatus);
				}
				catch (Throwable ex) {
					logger.debug("TransactionSynchronization.afterCompletion threw exception", ex);
				}
			}
		}
	}

看到上面我们也就大概明白为什么事务提交后能回调我们写的方法。最后回过头再重新看一下TransactionSynchronizationManager.registerSynchronization()这个方法里面做了什么,就是把我们注册的方法加到了synchs这个set集合里面

	public static void registerSynchronization(TransactionSynchronization synchronization)
			throws IllegalStateException {

		Assert.notNull(synchronization, "TransactionSynchronization must not be null");
		Set<TransactionSynchronization> synchs = synchronizations.get();
		if (synchs == null) {
			throw new IllegalStateException("Transaction synchronization is not active");
		}
		synchs.add(synchronization);
	}

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值