@Transactional과 롤백

2024. 6. 23. 08:58TIL

✔오늘 배운 중요한 🔑 point

  • Spring AOP로 구현된 @Transactional 의 Aspect에서는 PlatfromTransactionalManager를 이용해서 커밋, 롤백 작업을 처리하게 된다.
  • @Transactional(propagation = Propagation.REQUIRES_NEW)를 사용해서 부분 롤백이나, 독릭적인 트랜잭션이 필요한 경우에 적용시킬 수 있다.

🎯 오늘 배운 내용

 

@Transactional 기본 개념

트랜잭션은 데이터베이스의 상태를 변경시키기 위해 수행하는 작업 단위이며 하나의 트랜잭션 내에서 수행된 작업은 모두 성공하거나 모두 실패해야한다는 특징을 가진다

 

@Transactional 동작 방식

Spring Transaction Abstraction 에서는 트랜잭션을 처리하기 위해 TransactionManager 를 사용, 

Spring MVC 환경에서는 PlatfromTransactionManager 가 사용된다

 

Spring AOP로 구현된 @Transactional 의 Aspect에서는 PlatfromTransactionalManager를 이용해서 커밋, 롤백 작업을 처리하게 된다.

 

 

 

https://javacodehouse.com/courses/spring-boot/lesson-9-spring-transaction-handling/

 

Spring Transaction Management In REST API With Spring Boot

Transactional Methods What happens when a method is transactional? The method executes inside of a transaction, if anything goes wrong in the middle of the method execution, the transaction will be rolled back, otherwise, it will be committed. Transactiona

javacodehouse.com

 

 

@Transactional 전파방식

 

REQUIRED  (default)

fun main() {
		firstTransactionMethod()
}

@Transactional
fun firstTransactionMethod() {
		secondTransactionMethod()
}


fun secondTransactionMethod() {

}

firstTransationMehod()가 부모 트랜잭션이며

부모 트랜잭션 안에 secondTransactionMehod가 있기 때문에 자동으로 secondTransactionMehod가 자식 트랜잭션이 된다. 

자식 트랜잭션은 따로 @Transactional 어노테이션을 작성하지 않아도 된다.

 

 

REQUIRES_NEW   ★ ★ ★ ★ ★ ★ ★

부모 트랜잭션과 자식 트랜잭션을 독립적으로 분리하고 싶을 때 사용한다

  1. 자식 트랜잭션에서 에러가 발생하더라도 부모 트랜잭션에 영향(Rollback 등)을 주고싶지 않을 때
  2. 부모 트랜잭션이 커밋되기 전까지 기다리지 않고 자식 트랜잭션을 즉시 커밋하고 싶을 때
  3. 부모 트랜잭션이 실패하더라도 자식 트랜잭션이 성공했다면 자식 트랜잭션에 대한 결과를 커밋하고 싶을 때

 

 

@Transactional
fun congratulationBirthday(members: List<Member>) {
		for (member in members) {
				member.addPoint(5000)
				memberRepository.save(member)
				sendEmailService.sendBirthDayMail(member)
		}
}

@Transactional
fun sendBirthDayMail(member: Member) {
		// ...
		if (member.id == 997) {
				throw RuntimeException("내가 임의로 발생시킨 에러다!")
		}	
}

사용자 1000명에게 포인트를 지급하는 로직에서 997번 ID의 회원에서 에러가 발생했다면 전체 1000명에 대한 롤백이 이루어질 것이다. 포인트 등은 롤백을 해도 문제가 없을수 있지만 이미 보내버린 메일은 롤백 할 수 없기 때문에 이러한 상황을 타개하기 위해서 REQUIRES_NEW 방식을 사용한다.

 

 

@Transactional
fun congratulationBirthday(members: List<Member>) {
		for (member in members) {
				member.addPoint(5000)
				memberRepository.save(member)
				try {
						sendEmailService.sendBirthDayMail(member)
				} catch(ex: RuntimeExcetion) {
						member.minusPoint(5000)
						memberRepository.save(member)
						// 발생한 예외가 회복된다!!
				}
		}
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
fun sendBirthDayMail(member: Member) {
		// ...
		if (member.id == 997) {
				throw RuntimeException("997번째 회원은 못줘!")
		}	
}

 

위 코드에서는 @Transactional(propagation = Propagation.REQUIRES_NEW)을 이용해서 전파방식을 설정하고 Try-Catch문으로 예외를 회복시키는 것을 확인할 수 있다.

이렇게 하면 특정 회원에 대한 이메일 전송 실패가 전체 트랜잭션에 영향을 미치지 않게 된다.

 

NOT_SUPPORTED 

  • 부모 트랜잭션이 있다면 해당 트랜잭션을 보류하고 트랜잭션이 없는채로 로직을 실행한다.
  • 부모 트랜잭션이 없다면 NEVER, SUPPORTS 와 동일하게 트랜잭션이 없는채로 로직을 실행한다.

 

@DataJpaTest
class MemberServiceTest {

		@Test
		@Transactional(propagation = Propagation.NOT_SUPPORTED)
		fun noTransactionTest() {
				// ...
		}
}

 

@DataTest 어노테이션은 기본적으로 모든 메소드에 @Transactional 설정이 되어있는데 특정 테스트에 대해서 @Transactional 없이 테이스트 코드를 작성하고 싶다면 NOT_SUPPORTED를 사용하면 된다.

 

 

🤔 어떻게 활용할까?

@Transcatinal의 전파방식에 따라서 함수 간의 트랜잭션 흐름을 제어할 수 있다.

📓 오늘의 한줄

"The only real mistake is the one from which we learn nothing."

- Henry Ford -

 

 

'TIL' 카테고리의 다른 글

CDN(Content Delivery Network)  (0) 2024.06.25
CAP 정리  (0) 2024.06.24
@DataJpaTest 를 이용한 통합 테스트 코드 작성하기  (0) 2024.06.22
@DataJpaTest를 이용해서 단위 테스트 코드 작성하기  (0) 2024.06.21
Projection  (0) 2024.06.20