<aside> 💡 TransactionAspectSupport 클래스의 트랜잭션 에러 핸들링 방법

</aside>

public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

	  @Nullable
		protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
				final InvocationCallback invocation) throws Throwable {
	
			// 코드 생략 ...
	
			// 내가 사용할 코드부분
			if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
				// Standard transaction demarcation with getTransaction and commit/rollback calls.
				TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
			
				Object retVal;
				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);
				}
			
				if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
					// Set rollback-only in case of Vavr failure matching our rollback rules...
					TransactionStatus status = txInfo.getTransactionStatus();
					if (status != null && txAttr != null) {
						retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
					}
				}
			
				commitTransactionAfterReturning(txInfo);
				return retVal;
			}
	
			// 코드 생략 ...
	
		}

}
  1. TransactionAspectSupport 클래스의 completeTransactionAfterThrowing() 메소드로 들어왔고 코드를 확인해보자.
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

		// 이 메소드가 동작한다.
		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;
					}
				}
			}
		}

}
  1. 이 코드 안에서는 예외가 발생하면 **transactionAttribute.rollbackOn()**이 동작한다. 여기서 사용되는 rolllbackOn() 메소드는 DefaultTransactionAttribute 클래스의 rollbackOn() 메서드를 사용하는데 그 코드는 다음과 같다.
public class DefaultTransactionAttribute extends DefaultTransactionDefinition implements TransactionAttribute {

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

}
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

		// 이 메소드가 동작한다.
		protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
		
				// 코드 생략... (여기가 unchecked Exception : RuntimeException을 잡아주는 부분이다.)

				// 여기는 checked Exception : Exception을 잡아주는 부분이다.
				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;
					}

				}
		}

}

<aside> 💡 그럼 일반적인 checked Exception을 rollback을 시키려면 어떻게해야할까?

</aside>

  1. @ Transactional 어노테이션 내부로 들어가보자
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
		
	/**
		 * Defines zero (0) or more exception {@linkplain Class classes}, 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, its subclasses,
		 * and its nested classes. See the {@linkplain Transactional class-level javadocs}
		 * for further details on rollback rule semantics and warnings regarding possible
		 * unintentional matches.
		 * @see #rollbackForClassName
		 * @see org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class)
		 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)
		 */
		Class<? extends Throwable>[] rollbackFor() default {};

}
  1. 어노테이션 내부의 코드에서 rollbackFor() 라는 메소드로 설정할수가 있다.
  2. 이제 아래와 같이 트랜잭션을 달아줄 코드 위에 적어주면 된다.
@Transactional(rollbackFor = Exception.class)

Spring의 @Transactional 어노테이션 코드 설명