序
本文主要研究一下spring的UnexpectedRollbackException
UnexpectedRollbackException
/**
* Thrown when an attempt to commit a transaction resulted
* in an unexpected rollback.
*
* @author Rod Johnson
* @since 17.03.2003
*/
@SuppressWarnings("serial")
public class UnexpectedRollbackException extends TransactionException {
/**
* Constructor for UnexpectedRollbackException.
* @param msg the detail message
*/
public UnexpectedRollbackException(String msg) {
super(msg);
}
/**
* Constructor for UnexpectedRollbackException.
* @param msg the detail message
* @param cause the root cause from the transaction API in use
*/
public UnexpectedRollbackException(String msg, Throwable cause) {
super(msg, cause);
}
}
UnexpectedRollbackException繼承了TransactionException,一般是事務巢狀,內層事務丟擲了異常,外層事務給catch住了,然後試圖提交事務報錯
示例
@Transactional
public Customer createWithCatch(String name, String email) {
Customer customer = customerRepository.save(new Customer(name, email));
try {
demoService.throwExInTx(0);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return customer;
}
// demoService
@Transactional
public void throwExInTx(int i) {
int result = 1 / i;
}
這裡內層事務throwExInTx丟擲了異常,而外層事務createWithCatch給catch住了,最後提交事務則報UnexpectedRollbackException異常
原始碼解析
invokeWithinTransaction
org/springframework/transaction/interceptor/TransactionAspectSupport.java
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
//......
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 (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;
}
//......
}
TransactionAspectSupport的invokeWithinTransaction方法在執行invocation.proceedWithInvocation()時會catch住異常,然後執行completeTransactionAfterThrowing
completeTransactionAfterThrowing
org/springframework/transaction/interceptor/TransactionAspectSupport.java
/**
* Handle a throwable, completing the transaction.
* We may commit or roll back, depending on the configuration.
* @param txInfo information about the current transaction
* @param ex throwable encountered
*/
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
completeTransactionAfterThrowing會判斷該異常是否要rollback,需要rollback的話,則執行txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus())
processRollback
org/springframework/transaction/support/AbstractPlatformTransactionManager.java
/**
* Process an actual rollback.
* The completed flag has already been checked.
* @param status object representing the transaction
* @throws TransactionException in case of rollback failure
*/
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);
}
}
processRollback這裡會判斷,如果該事務外層還有事務,則判斷status.isLocalRollbackOnly()或者是isGlobalRollbackOnParticipationFailure(預設返回true
),然後執行doSetRollbackOnly(status)
doSetRollbackOnly
org/springframework/orm/jpa/JpaTransactionManager.java
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
JpaTransactionObject txObject = (JpaTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Setting JPA transaction on EntityManager [" +
txObject.getEntityManagerHolder().getEntityManager() + "] rollback-only");
}
txObject.setRollbackOnly();
}
doSetRollbackOnly會獲取txObject執行setRollbackOnly
setRollbackOnly
org/springframework/orm/jpa/JpaTransactionManager.java
public void setRollbackOnly() {
EntityTransaction tx = getEntityManagerHolder().getEntityManager().getTransaction();
if (tx.isActive()) {
tx.setRollbackOnly();
}
if (hasConnectionHolder()) {
getConnectionHolder().setRollbackOnly();
}
}
setRollbackOnly這裡會執行tx.setRollbackOnly()、getConnectionHolder().setRollbackOnly()標記為rollbackOnly
外層事務processCommit
org/springframework/transaction/support/AbstractPlatformTransactionManager.java
/**
* Process an actual commit.
* Rollback-only flags have already been checked and applied.
* @param status object representing the transaction
* @throws TransactionException in case of commit failure
*/
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);
}
}
processCommit在unexpectedRollback為true的時候會丟擲UnexpectedRollbackException(Transaction silently rolled back because it has been marked as rollback-only
);這裡是status.isGlobalRollbackOnly()被標記為true了,因而unexpectedRollback為true
小結
UnexpectedRollbackException繼承了TransactionException,一般是事務巢狀,內層事務丟擲了異常,外層事務給catch住了,然後試圖提交事務報錯UnexpectedRollbackException(Transaction silently rolled back because it has been marked as rollback-only
)。