@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.java 의 invokeWithinTransaction 함수를 실행
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.java 의 invokeWithinTransaction 에서 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에서 롤백이 안되는지 명확하게 알 수 있는 기회였다.