@TransactionalEventListener 失效

文章讨论了在Spring框架中,TransactionalEventListener在特定情况下失效,原因在于新事务创建和线程绑定的交互。作者通过追踪代码逻辑,发现事务结束时新事务标志newTransaction为false导致事件监听器未被触发。解决方法是通过异步监听避免两次事务处理在同一线程中发生。
摘要由CSDN通过智能技术生成

记录开发过程中的一次@TransactionalEventListener 失效

先把代码贴出来


@Service
public class EventService {
    @Autowired
    @Lazy
    private EventService eventService;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Transactional
    public void test(){
        eventService.aPublisher();
    }


    public void aPublisher(){
        System.out.println("发布A事件");
        Aevent aevent = new Aevent(new Object());
        eventPublisher.publishEvent(aevent);
    }

    @Transactional
    public void atest(){
        System.out.println("调用atest");
        eventService.bPublisher();
    }


    public void bPublisher(){
        System.out.println("发布B事件");
        Bevent bevent = new Bevent(new Object());
        eventPublisher.publishEvent(bevent);
    }
    @Transactional
    public void btest(){
        System.out.println("调用btest");
    }

    @TransactionalEventListener
    //@Async
    public void aListener(Aevent aevent){
        eventService.atest();
    }


    @TransactionalEventListener

    public void bListener(Bevent aevent){
        eventService.btest();
    }
  }

预想流程
发布A事件 > 调用atest >发布B事件>调用atest
实际流程
发布A事件 > 调用atest >发布B事件
虽然发布了Bevent事件,但是没有调用atest

一步步跟发现进入了
当 test() 执行完成后会第一次进入 TransactionSynchronizationEventAdapter#afterCompletion 此时 status =STATUS_COMMITTED
然后会执行 atest > bPublisher,
当 atest() 执完成后会第二次进入 TransactionSynchronizationEventAdapter#afterCompletion 此时 status=STATUS_UNKNOWN,不满足条件不会执行 processEvent()

org.springframework.transaction.event.ApplicationListenerMethodTransactionalAdapter.TransactionSynchronizationEventAdapter#afterCompletion
其中 this.phase = TransactionPhase.AFTER_COMMIT,status = STATUS_UNKNOWN =2
不满足下面的任何一条判断,所以没有调用 processEvent() 也就没有调用 回调方法
现在主要的问题是 为什么 status = STATUS_UNKNOWN

	@Override
		public void afterCompletion(int status) {
			if (this.phase == TransactionPhase.AFTER_COMMIT && status == STATUS_COMMITTED) {
				processEvent();
			}
			else if (this.phase == TransactionPhase.AFTER_ROLLBACK && status == STATUS_ROLLED_BACK) {
				processEvent();
			}
			else if (this.phase == TransactionPhase.AFTER_COMPLETION) {
				processEvent();
			}
		}

最后定位到
org.springframework.transaction.support.AbstractPlatformTransactionManager#triggerAfterCompletion
通过分析发现 status.isNewTransaction() = false
导致进入了registerAfterCompletionWithExistingTransaction()
而 status.isNewTransaction() = false 又是因为 newTransaction = false
现在就来找 为什么newTransaction = false

private void triggerAfterCompletion(DefaultTransactionStatus status, int completionStatus) {
		if (status.isNewSynchronization()) {
			List<TransactionSynchronization> synchronizations = TransactionSynchronizationManager.getSynchronizations();
			TransactionSynchronizationManager.clearSynchronization();
			if (!status.hasTransaction() || status.isNewTransaction()) {
				if (status.isDebug()) {
					logger.trace("Triggering afterCompletion synchronization");
				}
				// 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.
				//就是这里会 传入status  = STATUS_UNKNOWN 
				registerAfterCompletionWithExistingTransaction(status.getTransaction(), synchronizations);
			}
		}
	}

定位到
org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction

handleExistingTransaction();
会使 newTransaction = false
最后定位到是 txObject.getConnectionHolder().isTransactionActive() =true

public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
		Object transaction = doGetTransaction();

		

		if (definition == null) {
			// Use defaults if no transaction definition given.
			definition = new DefaultTransactionDefinition();
		}

		if (isExistingTransaction(transaction)) {
			//进入这里会 把newTransaction = false
			return handleExistingTransaction(definition, transaction, debugEnabled);
		}

	....
	}

定位到
org.springframework.transaction.support.TransactionSynchronizationManager#getResource
这个方法是从当前线程中获取 ConnectionHolder
当 EventService#test() 执行完成后 ThreadLocal 就有了一个 ConnectionHolder.isTransactionActive() =true

	public static Object getResource(Object key) {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		//从ThreadLocal 获取当前线程的 ConnectionHolder
		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;
	}

org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

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

		try {
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
				Connection newCon = this.dataSource.getConnection();
				if (logger.isDebugEnabled()) {
					logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
				}
				//设置ConnectionHolder
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			}

			txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
			con = txObject.getConnectionHolder().getConnection();

			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
			txObject.setPreviousIsolationLevel(previousIsolationLevel);

			// 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");
				}
				con.setAutoCommit(false);
			}

			prepareTransactionalConnection(con, definition);
			//ConnectionHolder.TransactionActive = 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.
			if (txObject.isNewConnectionHolder()) {
			//把ConnectionHolder 设置到 ThreadLoacl 中
				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);
		}
	}

现在知道在哪里造成的了最后再总结一下
status == STATUS_UNKNOWN-> newTransaction = false -> transactionActive=true -> ThreadLocal 获取当前线程的 ConnectionHolder.transactionActive=true-> 第一次进入切面会把 ConnectionHolder .transactionActive=true 设置到 ThreadLoacl 中

第一次进入 事务切面,commit 时 newTransaction = true -> status == STATUS_COMMITTED
满足 this.phase == TransactionPhase.AFTER_COMMIT && status == STATUS_COMMITTED
然后进入回调方法 atest (),执行完成调用 commit 时 newTransaction = false -> status == STATUS_UNKNOWN
无法满足 回调条件 所以没有 回调 btest() ,

因为两次进入切面都有同一个线程,导致的,所以我们只需要然两次进入切面不是同一线程就可以了,那么就只需要异步监听就行了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值