diff --git a/common/src/main/java/org/apache/seata/common/Constants.java b/common/src/main/java/org/apache/seata/common/Constants.java index d3a0ab512d2..43da1827e05 100644 --- a/common/src/main/java/org/apache/seata/common/Constants.java +++ b/common/src/main/java/org/apache/seata/common/Constants.java @@ -225,4 +225,14 @@ public interface Constants { */ String JACKSON_JSON_TEXT_PREFIX = "{\"@class\":"; + /** + * The constant DEAD_LOCK_SQL_STATE + */ + String DEAD_LOCK_SQL_STATE = "40001"; + + /** + * The constant DEAD_LOCK_ERROR_CODE + */ + int DEAD_LOCK_ERROR_CODE = 1213; + } diff --git a/spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java b/spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java index 95ada3dc090..88003f6f4fb 100644 --- a/spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java +++ b/spring/src/main/java/org/apache/seata/rm/fence/SpringFenceHandler.java @@ -18,8 +18,12 @@ import java.lang.reflect.Method; import java.sql.Connection; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; @@ -28,6 +32,7 @@ import javax.sql.DataSource; +import org.apache.seata.common.Constants; import org.apache.seata.common.exception.ExceptionUtil; import org.apache.seata.common.exception.FrameworkErrorCode; import org.apache.seata.common.exception.SkipCallbackWrapperException; @@ -41,10 +46,14 @@ import org.apache.seata.integration.tx.api.fence.store.CommonFenceStore; import org.apache.seata.integration.tx.api.fence.store.db.CommonFenceStoreDataBaseDAO; import org.apache.seata.integration.tx.api.remoting.TwoPhaseResult; +import org.apache.seata.rm.tcc.api.BusinessActionContext; +import org.apache.seata.rm.tcc.api.BusinessActionContextUtil; +import org.apache.seata.rm.tcc.utils.MethodUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionTemplate; /** @@ -108,7 +117,8 @@ public static void setTransactionTemplate(TransactionTemplate transactionTemplat */ @Override public Object prepareFence(String xid, Long branchId, String actionName, Callback targetCallback) { - return transactionTemplate.execute(status -> { + TransactionTemplate template = createTransactionTemplateForTransactionalMethod(null); + return template.execute(status -> { try { Connection conn = DataSourceUtils.getConnection(dataSource); boolean result = insertCommonFenceLog(conn, xid, branchId, actionName, CommonFenceConstant.STATUS_TRIED); @@ -146,7 +156,8 @@ public Object prepareFence(String xid, Long branchId, String actionName, Callbac @Override public boolean commitFence(Method commitMethod, Object targetTCCBean, String xid, Long branchId, Object[] args) { - return transactionTemplate.execute(status -> { + TransactionTemplate template = createTransactionTemplateForTransactionalMethod(MethodUtils.getTransactionalAnnotationByMethod(commitMethod, targetTCCBean)); + return template.execute(status -> { try { Connection conn = DataSourceUtils.getConnection(dataSource); CommonFenceDO commonFenceDO = COMMON_FENCE_DAO.queryCommonFenceDO(conn, xid, branchId); @@ -188,7 +199,8 @@ public boolean commitFence(Method commitMethod, Object targetTCCBean, @Override public boolean rollbackFence(Method rollbackMethod, Object targetTCCBean, String xid, Long branchId, Object[] args, String actionName) { - return transactionTemplate.execute(status -> { + TransactionTemplate template = createTransactionTemplateForTransactionalMethod(MethodUtils.getTransactionalAnnotationByMethod(rollbackMethod, targetTCCBean)); + return template.execute(status -> { try { Connection conn = DataSourceUtils.getConnection(dataSource); CommonFenceDO commonFenceDO = COMMON_FENCE_DAO.queryCommonFenceDO(conn, xid, branchId); @@ -218,6 +230,16 @@ public boolean rollbackFence(Method rollbackMethod, Object targetTCCBean, return result; } catch (Throwable t) { status.setRollbackOnly(); + Throwable cause = t.getCause(); + if (cause != null && cause instanceof SQLException) { + SQLException sqlException = (SQLException) cause; + String sqlState = sqlException.getSQLState(); + int errorCode = sqlException.getErrorCode(); + if (Constants.DEAD_LOCK_SQL_STATE.equals(sqlState) && Constants.DEAD_LOCK_ERROR_CODE == errorCode) { + // MySQL deadlock exception + LOGGER.error("Common fence rollback fail. xid: {}, branchId: {}, This exception may be due to the deadlock caused by the transaction isolation level being Repeatable Read. The seata server will try to roll back again, so you can ignore this exception. (To avoid this exception, you can set transaction isolation to Read Committed.)", xid, branchId); + } + } throw new SkipCallbackWrapperException(t); } }); @@ -353,6 +375,31 @@ private static void addToLogCleanQueue(final String xid, final long branchId) { } } + /** + * Creating a transactionTemplate with business transactional attributes + * @param transactional Transactional annotation + * @return + */ + private TransactionTemplate createTransactionTemplateForTransactionalMethod(Transactional transactional) { + Map businessActionContext = Optional.ofNullable(BusinessActionContextUtil.getContext()).map(BusinessActionContext::getActionContext).orElse(null); + if (transactional == null && businessActionContext == null) { + return transactionTemplate; + } + if (transactional != null) { + TransactionTemplate template = new TransactionTemplate(Objects.requireNonNull(transactionTemplate.getTransactionManager())); + template.setIsolationLevel(transactional.isolation().value()); + return template; + } else { + boolean containIsolation = businessActionContext.containsKey(Constants.TX_ISOLATION); + if (!containIsolation) { + return transactionTemplate; + } + TransactionTemplate template = new TransactionTemplate(Objects.requireNonNull(transactionTemplate.getTransactionManager())); + template.setIsolationLevel((int) businessActionContext.get(Constants.TX_ISOLATION)); + return template; + } + } + /** * clean fence log that has the final status runnable. * diff --git a/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java b/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java index eaaa76494cd..2ad3c2b373f 100644 --- a/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java +++ b/tcc/src/main/java/org/apache/seata/rm/tcc/TCCResourceManager.java @@ -119,6 +119,8 @@ public BranchStatus branchCommit(BranchType branchType, String xid, long branchI applicationData); Object[] args = this.getTwoPhaseCommitArgs(tccResource, businessActionContext); + //share actionContext implicitly + BusinessActionContextUtil.setContext(businessActionContext); Object ret; boolean result; // add idempotent and anti hanging @@ -146,6 +148,9 @@ public BranchStatus branchCommit(BranchType branchType, String xid, long branchI String msg = String.format("commit TCC resource error, resourceId: %s, xid: %s.", resourceId, xid); LOGGER.error(msg, ExceptionUtil.unwrap(t)); return BranchStatus.PhaseTwo_CommitFailed_Retryable; + } finally { + // clear the action context + BusinessActionContextUtil.clear(); } } @@ -177,6 +182,8 @@ public BranchStatus branchRollback(BranchType branchType, String xid, long branc BusinessActionContext businessActionContext = BusinessActionContextUtil.getBusinessActionContext(xid, branchId, resourceId, applicationData); Object[] args = this.getTwoPhaseRollbackArgs(tccResource, businessActionContext); + //share actionContext implicitly + BusinessActionContextUtil.setContext(businessActionContext); Object ret; boolean result; // add idempotent and anti hanging @@ -205,6 +212,9 @@ public BranchStatus branchRollback(BranchType branchType, String xid, long branc String msg = String.format("rollback TCC resource error, resourceId: %s, xid: %s.", resourceId, xid); LOGGER.error(msg, ExceptionUtil.unwrap(t)); return BranchStatus.PhaseTwo_RollbackFailed_Retryable; + } finally { + // clear the action context + BusinessActionContextUtil.clear(); } }