이번 시간에는 spring transaction 전파에 대해서 알아볼 예정이다.
transaction 전파란 현재 transaction 에서 다른 transaction 으로 이동할 때를 이야기 한다.
예를들어 AccountService에 transaction이 걸려 있는데 다른 OrderService 에서도 transaction 이 걸려 있는 것을 말한다.
같은 클래스는 해당 사항이 없다.

간단하게 확인 가능한 테스트 코드를 보자.
아래는 AccountService클래스 이다.

@Autowired
private TransactionService transactionService;

@Transactional
public AccountTest transactionTest(AccountTest accountTest) {
  log.info("currentTransactionName : {}", TransactionSynchronizationManager.getCurrentTransactionName());
  transactionService.transactionService();
  return accountRepository.save(accountTest);
}

이번에는 TransactionService이다.

public void transactionService(){
  log.info("currentTransactionName : {}" , TransactionSynchronizationManager.getCurrentTransactionName());
}

그냥 현재 트랜잭션 명을 출력해주는 그런 코드이다.
일단 기본적으로 위와 테스트를 돌려보자.

@Test
public void transactionTest(){
  AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(RootConfiguration.class);
  AccountService bean = annotationConfigApplicationContext.getBean(AccountService.class);
  AccountTest accountTest = new AccountTest();
  accountTest.setName("wonwoo");
  AccountTest save = bean.transactionTest(accountTest);
  System.out.println(save);
}

로그를 확인해보면 다음과 같다.

21:41:55.043 [main] INFO  me.wonwoo.service.AccountService - currentTransactionName : me.wonwoo.service.AccountService.transactionTest
21:41:55.043 [main] INFO  me.wonwoo.service.TransactionService - currentTransactionName : me.wonwoo.service.AccountService.transactionTest

똑같은 트랜잭션 명이다. 그럼 이번에는 transactionService 메서드에 @Transactional을 달아보자.

@Transactional
public void transactionService() {
  log.info("currentTransactionName : {}", TransactionSynchronizationManager.getCurrentTransactionName());
}

그리고 나서 확인해보면 아래와 같은 로그가 출력 될 것이다.

21:43:29.874 [main] INFO  me.wonwoo.service.AccountService - currentTransactionName : me.wonwoo.service.AccountService.transactionTest
21:43:29.896 [main] INFO  me.wonwoo.service.TransactionService - currentTransactionName : me.wonwoo.service.AccountService.transactionTest

위에 것과 동일하게 동일한 명으로 출력된다. 기본적으로 Spring의 트랜잭션은 전파되는 것을 알수 있다.
@Transactional속성 중 Propagation를 보면 전파 속성을 지정할 수 있다. 기본값은 Propagation.REQUIRED이다.
이번에는 다른 속성을 넣어보자.

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transactionService() {
  log.info("currentTransactionName : {}", TransactionSynchronizationManager.getCurrentTransactionName());
}

위와 같이 해서 테스트를 돌려본다면 아래와 같은 로그를 확인 할 수 있다.

21:46:41.782 [main] INFO  me.wonwoo.service.AccountService - currentTransactionName : me.wonwoo.service.AccountService.transactionTest
21:46:41.802 [main] INFO  me.wonwoo.service.TransactionService - currentTransactionName : me.wonwoo.service.TransactionService.transactionService

트랜잭션 명이 다르다. 위와 속성은 새로 트랜잭션을 만드는 것이다. 만약 기존에 트랜잭션이 있더라도 현재 트랜잭션을 새로 만드는 속성이다.

@Transactional(propagation = Propagation.NEVER)
public void transactionService() {
  log.info("currentTransactionName : {}", TransactionSynchronizationManager.getCurrentTransactionName());
}

다음은 Propagation.NEVER 속성이다. 이것을 테스트를 해보면 에러가 발생한다. 아래와 같은 에러가 발생한다.

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'

트랜잭션이 이미 있으면 에러를 발생한다. 만약 트랜잭션이 시작된것이 없다면 기존과 동일하게 트랜잭션을 시작한다.

다음으로는 Propagation.MANDATORY 속성에 대해 보자.

@Transactional(propagation = Propagation.MANDATORY)
public void transactionService() {
  log.info("currentTransactionName : {}", TransactionSynchronizationManager.getCurrentTransactionName());
}

이것을 테스트를 해보면 트랜잭션이 전파 된다. 로그는 아래와 같다.

21:53:03.336 [main] INFO  me.wonwoo.service.AccountService - currentTransactionName : me.wonwoo.service.AccountService.transactionTest
21:53:03.350 [main] INFO  me.wonwoo.service.TransactionService - currentTransactionName : me.wonwoo.service.AccountService.transactionTest

하지만 만약 트랜잭션이 없는 곳에서 호출 된다면 다음과 같은 에러가 발생된다.

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

이번에는 NOT_SUPPORTED 속성이다.

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void transactionService() {
  log.info("currentTransactionName : {}", TransactionSynchronizationManager.getCurrentTransactionName());
  AccountTest accountTest = new AccountTest();
  accountTest.setName("wonwoo123");
  accountRepository.save(accountTest);

}

NOT_SUPPORTED 새로운 트랜잭션이 생성된다. 새로운 트랜잭션이라면 Propagation.REQUIRES_NEW 와 동일한 기능이 아닌가? 하지만 약간 다르다. Propagation.REQUIRES_NEW는 새롭긴 하지만 부모(새로만들어지기전) 트랜잭션의 영향이 있다. 부모 트랜잭션이 에러가 발생하면 새로 만들어진 트랜잭션도 롤백이 된다.한마디로 기존 트랜잭션을 잠시 보류하고 새로운 트랜잭션을 진행한 후에 다시 기존 트랜잭션이 동작하게 된다. NOT_SUPPORTED 경우에는 별개다 만약 부모 트랜잭션에서 오류가 발생하여 롤백을 해도 새로운 트랜잭션은 롤백되지 않는다.

@Transactional
public AccountTest transactionTest(AccountTest accountTest) {
  log.info("currentTransactionName : {}", TransactionSynchronizationManager.getCurrentTransactionName());
  transactionService.transactionService();
  AccountTest save = accountRepository.save(accountTest);
  if (true) {
    throw new RuntimeException();
  }

  return save;
}

이와 같이 강제로 에러를 발생하게 했다.

이외에도 몇가지가 더 2가지 정도다 더 있다. 하지만 필지가 정확하게 이해 한 것이 아니기에 나머지는 테스트 해보지 않았다. 이상으로 트랜잭션 전파의 테스트를 진행 해봤다.