-
왜 이게 롤백 되는거지!?!!?!알아두면 좋은것 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"); }
롤백이 마크되서 롤백이된다.
근데 다시 생각해보면 잡을꺼면 왜 트랜젝션을 걸었을까?하하하....'알아두면 좋은것' 카테고리의 다른 글
traceroute 란.......... (0) 2021.08.20 Kafka (0) 2021.07.10 의존성 주입 DI (Dependency Injection) 클래스 주입 (0) 2020.09.08 웹서버 / WAS (1) 2020.09.06 템플릿 메소드 패턴 / 팩토리 메소드 패턴 / 팩토리 메소드 (0) 2020.09.05 댓글