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