передовой
Авария на производстве наводит на размышления. После ручного перехвата исключений в декларативной транзакции Spring фактически определено, что она подлежит откату. Что это за операция? Нечего сказать, сразу к коду
@Service
public class A {
@Autowired
private B b;
@Autowired
private C c;
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT)
public void operate() {
try {
b.insertB();
c.insertC();
}catch (Exception e) {
e.printStackTrace();
}
}
}
@Service
public class B {
@Autowired
private BM bm;
@Transactional(propagation = Propagation.REQUIRED)
public int insertB() {
return bm.insert("B");
}
}
@Service
public class C {
@Autowired
private CM cm;
@Transactional(propagation = Propagation.REQUIRED)
public int insertC() {
return cm.insert("C");
}
}
постановка задачи
Ну, код выше все видели.В нормальных условиях мы вставляем часть данных в таблицу B и таблицу C, так что же происходит, когда код ненормальный?
Теперь мы предполагаем, что B успешно вставляет данные, но C не может вставить данные. В это время исключение будет выброшено для A, которое будет вставлено в A.operate
Захваченный методом try-cache метода, обычно в это время B может вставить запись в базу данных, но таблица C не вставляется, что мы и ожидаем, но на самом деле это не так, фактическая ситуация такова, что B таблица не вставляет данные, таблица C Данные не были вставлены, что означает, что Spring откатил всю операцию.
будь осторожен
Если код немного изменится, поместите try-cache в
insertC
В блоке кода по тому же сценарию запись будет успешно вставлена в B
Состояние фронта точки знаний
Если вы понимаете механизм распространения Spring, вы можете пропустить его напрямую.
Сначала нам нужно выяснить, что находится в Spring
REQUIRED
рольТРЕБУЕТСЯ: Если текущей транзакции нет, создать новую транзакцию, если есть текущая транзакция, присоединиться к текущей транзакции
То есть, когда наш механизм распространения одновременно
REQUIRED
Когда транзакция A, B и C является общей, операция фиксации или отката будет выполняться только после завершения процесса A, а фиксация и откат не будут выполняться во время выполнения B или C.
Отслеживание проблем
Что ж, с некоторым запасом знаний, давайте посмотрим исходники вместе
Сначала мы находим запись прокси для транзакций SpringTransactionInterceptor
, когда мы вызываемoperate
метод будет вызыватьсяTransactionInterceptor
изinvoke
метод, это точка входа всей сделки, давайте посмотрим непосредственно на ключевые точкиinvoke
серединаinvokeWithinTransaction
метод
//获取事务属性类 AnnotationTransactionAttributeSource
TransactionAttributeSource tas = getTransactionAttributeSource();
//获取事务属性
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
//获取事务管理器
final TransactionManager tm = determineTransactionManager(txAttr);
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
//获取joinpoint
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
//注解事务会走这里
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;
}
Я опустил неважный код, поэтому давайте посмотрим на процесс.Вышеприведенный код явно отражен.Когда во время выполнения нашей программы будет выброшено исключение, оно будет вызваноcompleteTransactionAfterThrowing
Операция отката, если не возникает исключения, в конечном итоге вызовет фиксацию транзакции.commitTransactionAfterReturning
, давайте проанализируем
Нормальная ситуация, когда в C происходит исключение, и выполнение прибываетcompleteTransactionAfterThrowing
Транзакция откатывается, но поскольку это не вновь созданная транзакция, а присоединенная транзакция, операция отката не будет запущена, и исключение будет перехвачено в A, и в конечном итоге оно перейдет кcommitTransactionAfterReturning
Транзакция совершается, это правда?
На самом деле так оно и есть,тогда странно.Я явно представил,но почему он откатился?Давайте дальше смотреть вниз.
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
// Use defaults if no transaction definition given.
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
//重点看.. DataSourceTransactionObject拿到对象
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
//第一次进来connectionHolder为空的, 所以不存在事务
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
//如果不是第一次进来, 则会走这个逻辑
return handleExistingTransaction(def, transaction, debugEnabled);
}
// Check definition settings for new transaction.
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
//第一次进来大部分会走这里(传播属性是 Required | Requested New | Nested)
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
//先挂起
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
//开启事务
return startTransaction(def, transaction, debugEnabled, suspendedResources);
} catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
} else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + def);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
Этот код является кодом для запуска транзакции. Давайте посмотрим, когда наш A впервые пришел, в это время не было транзакции, поэтомуisExistingTransaction
Метод не установлен, идем вниз, потому что наш механизм распространения НЕОБХОДИМ, поэтому мы перейдем кstartTransaction
метод
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
//创建一个新的事务状态, 注意这里的newTransaction 属性为true
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
//开启事务
doBegin(transaction, definition);
//开启事务后, 改变事务状态
prepareSynchronization(status, definition);
return status;
}
Ну, здесь нам просто нужно сосредоточиться на одном моменте, и этоnewTransactionStatus
третий параметрnewTransaction
, только когда мы создаем новую транзакциюtrue
,Это свойство очень важно, мы будем использовать его позже
Ну вот первая транзакция завершена, а дальше будем вызывать бизнес-логику.При вызове insertB мы перейдем кgetTransaction
, продолжаем смотреть на него, на этот разisExistingTransaction
Вы можете получить значение, потому что A уже создал транзакцию для нас, и она будет вызвана в это время.handleExistingTransaction
метод
//如果第二次进来还是PROPAFGATION_REQUIRED, 走这里, newTransation为false
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
Полезным кодом для REQUIRED является это предложение, а все остальные читать не нужно.Так же мы видим третий параметр newTransaction, который здесь false, указывая на то, что предыдущая транзакция добавлена, а не заново создана вами, а затем бизнес-код выполнен, наконец, переходим к коммиту, посмотрим, что делается в коммите
//如果有回滚点
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();
}
Ничего не делает, почему? потому что нашnewTransaction
неверно, поэтому, когда наш код находится вoperate
Он придет сюда после выполнения всех методов.
Хорошо, посмотримinsertC
, предыдущий процесс точно такой же, мы прямо видим код отката
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to 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");
}
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);
}
}
нашinsertC
Тот же метод, который он не соответствует правду, это в конечном итоге придетdoSetRollbackOnly
,Сюдавысший приоритет, и, наконец, вызовите этот фрагмент кода
public void setRollbackOnly() {
this.rollbackOnly = true;
}
Затем мы выполним код нашего ключа Aoperate
отправил код
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);
}
Ну, все понимают, когда вы видите это, в коммите, Spring рассудитdefStatus.isGlobalRollbackOnly
Было ли выброшено исключение, которое было перехвачено Spring, если это так, то операция фиксации не будет выполнена, а вместо этого будет выполнено выполнениеprocessRollback
операция отката
Суммировать
В Spring REQUIRED, пока Spring перехватывает исключение, Spring в конечном итоге откатит всю транзакцию, даже если она была перехвачена в бизнесе.
Итак, вернемся к исходному коду.Если мы хотим, чтобы Spring не откатывался, то нам нужно всего лишь поставить метод try-cache в метод insertC, потому что выброшенное в это время исключение не будет перехвачено Spring.