@Transactional과 Exception 간의 관계

지니·2024년 4월 1일
0

1. 트랜잭션 내 메소드 실행 중 예외 발생

특정 메소드를 트랜잭션 내에서 수행하도록 하고싶을 때 @Transactional 메소드를 선언하여 사용할 수 있다. 아래와 같이 말이다.

@Transactional
public void test() {
	// 구현
}


그런데 로직 수행 중 예외가 발생하면 지금까지 수행했던 로직은 어떻게 처리될까?

By default, a transaction will be rolled back on {@link RuntimeException} and {@link Error} but not on checked exceptions (business exceptions).

기본적으로 스프링 프레임워크에서는 CheckedException일 경우 지금까지의 내용은 커밋하고 UnCheckedException일 경우 모두 롤백한다. 스프링은 EJB 규칙을 따르기 때문이다.



실제 코드

내부가 궁금해서 열고 들어가봤더니 예외가 발생된 이후 롤백하는 메소드가 존재한다. txInfo.transactionAttribute.rollbackOn(ex) 에 주목하자. 해당 메소드에서 UnCheckedException이나 Error가 발생하면 던지는 것을 볼 수 있다.

/**
 * TransactionAspectSupport.java
 */
private Mono<Void> completeTransactionAfterThrowing(@Nullable ReactiveTransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.getReactiveTransaction() != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
                    "] after exception: " + ex);
        }
        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
            return txInfo.getTransactionManager().rollback(txInfo.getReactiveTransaction()).onErrorMap(ex2 -> {
                        logger.error("Application exception overridden by rollback exception", ex);
                        if (ex2 instanceof TransactionSystemException systemException) {
                            systemException.initApplicationException(ex);
                        }
                        else {
                            ex2.addSuppressed(ex);
                        }
                        return ex2;
                    }
            );
        }
        else {
            // We don't roll back on this exception.
            // Will still roll back if TransactionStatus.isRollbackOnly() is true.
            return txInfo.getTransactionManager().commit(txInfo.getReactiveTransaction()).onErrorMap(ex2 -> {
                        logger.error("Application exception overridden by commit exception", ex);
                        if (ex2 instanceof TransactionSystemException systemException) {
                            systemException.initApplicationException(ex);
                        }
                        else {
                            ex2.addSuppressed(ex);
                        }
                        return ex2;
                    }
            );
        }
    }
    return Mono.empty();
}
/**
 * DefaultTransactionAttribute
 */
@Override
public boolean rollbackOn(Throwable ex) {
    return (ex instanceof RuntimeException || ex instanceof Error);
}




checked exception 발생 시 롤백하고 싶다면?

Transactional 메소드는 rollbackFor 속성을 제공한다. (자매품 rollbackForClassName())

public @interface Transactional {
	/**
	 * Defines zero (0) or more exception {@linkplain Class types}, which must be
	 * subclasses of {@link Throwable}, indicating which exception types must cause
	 * a transaction rollback.
	 * <p>By default, a transaction will be rolled back on {@link RuntimeException}
	 * and {@link Error} but not on checked exceptions (business exceptions). See
	 * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)}
	 * for a detailed explanation.
	 * <p>This is the preferred way to construct a rollback rule (in contrast to
	 * {@link #rollbackForClassName}), matching the exception type and its subclasses
	 * in a type-safe manner. See the {@linkplain Transactional class-level javadocs}
	 * for further details on rollback rule semantics.
	 * @see #rollbackForClassName
	 * @see org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class)
	 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
	 */
    Class<? extends Throwable>[] rollbackFor() default {};
}


실제 코드

RuleBasedTransactionAttribute의 rollbackOn()으로 동작하게 된다. 안에 보이는 rollbackRules에 해당하는 값들이 위 어노테이션에서 설정한 값들이다. 만약 발생한 exception이 rollbackRules에 해당하지 않는다면 상위 rollbackOn()을 호출하게 되는데 이는 위에서 봤던 DefaultTransactionAttribute가 되어 UnCheckedException 혹은 Error일 경우 롤백하도록 한다.

	/**
	 * Winning rule is the shallowest rule (that is, the closest in the
	 * inheritance hierarchy to the exception). If no rule applies (-1),
	 * return false.
	 * @see TransactionAttribute#rollbackOn(java.lang.Throwable)
	 */
	@Override
	public boolean rollbackOn(Throwable 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;
				}
			}
		}

		// User superclass behavior (rollback on unchecked) if no rule matches.
		if (winner == null) {
			return super.rollbackOn(ex);
		}

		return !(winner instanceof NoRollbackRuleAttribute);
	}

profile
Coding Duck

0개의 댓글