Search
Duplicate

[Java] @Transcation try ~ catch 롤백 안되는 원인

순서
5
날짜
2023/07/29
사람
상태
Done
@Transaction Try ~ catch를 적용할 경우
@Transactional public Long save(Object object) { Long saveId; try { saveId = repository.save(Object); } catch (RuntimeException re ) { log.error("save fail, message: {},", re.getMessage()); } return saveId; }
Java
복사

문제발생

@Transaction안에 try ~ catch로 감싸져있는 코드들이 있었다. 문제는 try ~ catch 안에 익셉션이 발생 했을때 롤백이 동작 되지 않는 것이다.
@Transcation에 대해 찾아보게 되었다.
Transcation 동작 시키면 아래의 순서로 동작 하게 된다.
1.
service 레이어에서 Exception 을 발생
2.
TransactionAspectSupport.javainvokeWithinTransaction 함수를 실행
3.
invokeWithinTransaction 의 try ~ catch의 completeTransactionAfterThrowing 를 실행
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // Standard transaction demarcation with getTransaction and commit/rollback calls. TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; 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); } commitTransactionAfterReturning(txInfo); return retVal; } ....
Java
복사
TransactionAspectSupport.java
1.
completeTransactionAfterThrowing 함수실행
2.
txInfo.transactionAttribute.rollbackOn(ex) 동작
protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) { if (txInfo != null && txInfo.hasTransaction()) { if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex); } if (txInfo.transactionAttribute.rollbackOn(ex)) { ...
Java
복사
구현체 RuleBasedTransactionAttribute.java
1.
rollbackOn 은 if (winner == null) 이면 super.rollbackOn(ex); 동작
@Override public boolean rollbackOn(Throwable ex) { if (logger.isTraceEnabled()) { logger.trace("Applying rules to determine whether transaction should rollback on " + ex); } RollbackRuleAttribute winner = null; int deepest = Integer.MAX_VALUE; if (this.rollbackRules != null) { for (RollbackRuleAttribute rule : this.rollbackRules) { int depth = rule.getDepth(ex); if (depth >= 0 && depth < deepest) { deepest = depth; winner = rule; } } } if (logger.isTraceEnabled()) { logger.trace("Winning rollback rule is: " + winner); } // User superclass behavior (rollback on unchecked) if no rule matches. if (winner == null) { logger.trace("No relevant rollback rule found: applying default rules"); return super.rollbackOn(ex); } return !(winner instanceof NoRollbackRuleAttribute); }
Java
복사
구현체 DefaultTransactionAttribute.java rollbackOn 을보면
RuntimeException
Error
에 대해서 롤백을 처리하는 것을 확인 할 수 있다.
@Override public boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error); }
Java
복사
@Transcation은 RuntimeException 과 Error 을 잡는 것을 확인하였다.
그렇다면 왜? @Transaction안에 try ~ catch에서는 롤백을 못하는 것인가?
그 이유는 서비스 레이어에서 try catch로 잡을 경우
TransactionAspectSupport.javainvokeWithinTransaction 에서 catch로 잡지 못하게 되서, 롤백이 진행이 안되는 것이다.

해결방법

try ~ catch 로 감싼 후 exception이 발생 했을 때 throw로 Unchecked Exception을 발생 시킨다.
@Transactional public Long save(Object object) { Long saveId; try { saveId = repository.save(Object); } catch (RuntimeException re ) { log.error("save fail, message: {},", re.getMessage()); throw new RuntimeException("UnChecked Exception"); } return saveId; }
Java
복사

후기

오늘은 트랜잭션에 대해 분석하게 되었다. 기본적으로 트랜잭션은 RuntimeException과 Error에 대해 롤백이 진행되며, checkException에 대해서는 catch에서 RuntimeException을 발생시켜 롤백 시켜 줄 수 있다는 것을 알 수 있었고, 추가로 왜 try catch에서 롤백이 안되는지 명확하게 알 수 있는 기회였다.