ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 왜 이게 롤백 되는거지!?!!?!
    알아두면 좋은것 2020. 9. 9. 21:04

    woowabros.github.io/experience/2019/01/29/exception-in-transaction.html

     

    응? 이게 왜 롤백되는거지? - 우아한형제들 기술 블로그

    이 글은 얼마 전 에러로그 하나에 대한 호기심과 의문으로 시작해서 스프링의 트랜잭션 내에서 예외가 어떻게 처리되는지를 이해하기 위해 삽질을 해본 경험을 토대로 쓰여졌습니다. 스프링의

    woowabros.github.io

     

    일을 하는데 분명히 Exception 을 잡았는데 왜 롤백을 하는거지..?!?! transactional 을 걸고 있긴 한데

    try { 
    
    } catch(e : RunException) {
    	log.error("EXCEPTION 발생! ${e.message}");
    }

    로 로그만 찍고 상위의 코드는 계속 수행하려고 했다....

    근데 뭐지? 왜지? 롤백이 된다....ㅋㅋㅋ

     

    그렇게 검색을해보니 나랑 같은 상황이였던 블로그를 보게 되었고

    나도 기억을 하기 위해서 정리해둬야지

     

    더보기
    • <5> 문제의 코드부분입니다. try/catch 없이 RuntimeExceptio이 던져지면서 트랜잭션 완료처리가 시작됩니다.
    • <6> RuntimeException 때문에 트랜잭션을 롤백할지 결정하는 규칙을 적용한다네요. 그런데, 딱히 지정된 규칙이 없어서 디폴트 규칙으로 가는군요.
    • <7> 디폴트라더니 급기야 롤백하기로 결정했나봅니다… 참여한 트랜잭션 실패를 선언하고 rollback-only 마킹을 합니다. 여기까지가 예외가 발생한 내부 트랜잭션의 완료처리였습니다.
    • <8> 안에서 발생한 예외를 최초 트랜잭션 메서드에서 잡았습니다.
    • <9> 최초 트랜잭션 메서드가 완료처리를 시작합니다
    • <10> 앞에서 예외는 잡았고 별 문제 없어 보이는 듯하여 최종커밋을 하려고합니다. 그런데 정작 커밋하려는 순간 roll-back only가 마킹되어 있다고 롤백을 해버립니다!

    우아한 형제들 블로그에서 발췌한 부분 입니다.

     

    1. 트랜젝션이 완료 되는 처리를 하는 곳을 따라가본다 (TransactionAspectSupport.java:543)

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

     

    롤백이 됐으니까 txInfo.transactionAttribute.rollbackOn(ex) 의 내부를 봐야하는데

    @Override
    public boolean rollbackOn(Throwable ex) {
    	return (ex instanceof RuntimeException || ex instanceof Error);
    }

    내부는 Error 이거나 RunTimeException 의 일부이면 트랜잭션을 롤백이 필요하다고 본다는 것이다... (.....헉....ㅡ_ㅡ...)

    그럼 롤백을 하는 이 내부를 들어가보면 txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());

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

    -------------- 해당하는 부분만 가지고 오면 

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

     

    1. 트랜젝션을 가지고 있으면

    2. localRollBackOnly / globalRollbackOnParicipationFailure 을 검사한다.

    이 두 값은 디폴트로 localRollBackOnly = false  ////// globalRollbackOnParicipationFailure=true

    이라서 여기서 doSetRollbackOnly 에 롤백을 해야한다고 마킹이 된다.

    그리고 쭉쭉 내려가다보면

    if (unexpectedRollback) {
    	throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");
    }

    롤백이 마크되서 롤백이된다.

     

    근데 다시 생각해보면 잡을꺼면 왜 트랜젝션을 걸었을까?

    하하하....

    댓글