Spring ‐ 스프링 트랜잭션 이해 - dnwls16071/Backend_Study_TIL GitHub Wiki

📚 스프링 트랜잭션 사용 방식

  • Spring - 스프링 트랜잭션
  • 트랜잭션 매니저를 사용하는 방법은 크게 2가지가 있다.
    • 선언적 트랜잭션 관리
      • @Transactional 어노테이션이 있으면 AOP에 의해 트랜잭션 처리 로직과 실제 서비스 실행 로직이 명확히 분리된다.
      • 프록시 덕분에 트랜잭션 처리 로직과 순수한 비즈니스 로직을 분리할 수 있다.
    • 프로그래밍 방식 트랜잭션 관리
      • 트랜잭션 매니저 또는 트랜잭션 템플릿 등을 사용해서 트랜잭션 관련 코드를 직접 작성하는 방식이다.

스크린샷 2025-01-31 오후 9 58 39

  • @Transactional 어노테이션이 특정 클래스나 메서드에 하나라도 있으면 트랜잭션 AOP는 프록시를 만들어 스프링 컨테이너에 등록한다.
  • 실제 객체 대신에 프록시인 CGLIB를 스프링 빈에 등록한다.

📚 트랜잭션 적용 위치

  • 스프링에서 우선순위는 항상 더 구체적이고 자세한 것이 높은 우선순위를 가진다.
  • 클래스에 적용하면 메서드는 자동 적용이 된다.
  • 인터페이스에도 @Transactional 어노테이션이 적용할 수 있다. 하지만 우선순위를 잘 고려해보면 아래와 같다.
    • 클래스 메서드(우선순위가 가장 높다)
    • 클래스 타입
    • 인터페이스 메서드
    • 인터페이스 타입(우선순위가 가장 낮다)

❗인터페이스에 @Transactional 어노테이션을 사용하는 것은 권장하지 않는다. AOP를 적용하는 방식에 따라서 인터페이스에 어노테이션을 두면 AOP가 적용되지 않는 경우가 있기 때문이다. 가급적 구체 클래스에 @Transactional 어노테이션을 사용하자.

📚 트랜잭션 AOP 주의 사항

  • @Transactional 어노테이션을 사용하면 트랜잭션 처리 로직과 비즈니스 로직을 명확히 분리한다고 강조했다.
  • 실제로 이 @Transactional 어노테이션 트랜잭션 처리 방식은 스프링 AOP를 이용한 방법의 선언적 트랜잭션 관리 방식이고 프록시 측에서 실제 서비스를 호출한다.
  • 만약 프록시를 거치지 않고 대상 객체를 직접 호출하게 되면 AOP가 적용되지 않고 트랜잭션도 적용되지 않는다.
  • AOP를 적용하게 되면 스프링은 대상 객체 대신에 프록시를 스프링 빈으로 등록한다.
@Slf4j
@SpringBootTest
public class InternalCallV1Test {

	@Autowired CallService callService;

	@Test
	void printProxy() {
		log.info("callService class={}", callService.getClass());
	}

	@Test
	void internalCall() {
		callService.internal();
	}
	@Test
	void externalCall() {
		callService.external();
	}

	@TestConfiguration
	static class InternalCallV1Config {

		@Bean
		CallService callService() {
			return new CallService();
		}
	}

	@Slf4j
	static class CallService {

		// 외부 직접 호출
		public void external() {
			log.info("call external");
			printTxInfo();
			internal();
		}

		// 내부 호출
		@Transactional
		public void internal() {
			log.info("call internal");
			printTxInfo();
		}

		private void printTxInfo() {
			boolean txActive = TransactionSynchronizationManager.isActualTransactionActive();
			log.info("tx active={}", txActive);
		}
	}
}

스크린샷 2025-02-02 오후 8 48 46

스크린샷 2025-02-02 오후 8 59 31

❗클라이언트 측에서 AOP Proxy callService의 external() 메서드를 호출하면 @Transactional 어노테이션이 적용되지 않았기 때문에 실제 객체 인스턴스의 external()을 호출하게 되고 내부에서 internal()를 호출하게 된다. ❗메서드 앞에 별도의 참조가 없으면 this라는 뜻으로 자기 자신의 인스턴스를 가리킨다. 결과적으로 자기 자신의 내부 메서드를 호출하기 때문에 AOP Proxy를 통한 메서드 호출이 이루어지지 않게 된다.

  • 스프링의 트랜잭션 AOP 기능은 public 메서드에만 트랜잭션을 적용하도록 기본 설정이 되어 있다.

📚 예외와 트랜잭션 커밋, 롤백

  • 스프링은 체크 예외는 커밋을 하고 언체크 예외는 롤백을 한다.
  • 비즈니스 상황에 따라 체크 예외의 경우에도 트랜잭션을 커밋하지 않고 롤백하고 싶을 수도 있다. 이 때는 rollbackFor 옵션을 사용한다.
  • 런타임 예외는 항상 롤백된다.