[Spring] @Transaction
@Transactional은 Spring 프레임워크에서 제공하는 애노테이션으로, 트랜잭션 관리를 간단하고 일관성 있게 처리할 수 있게 해줍니다. 데이터베이스 작업에서 트랜잭션의 시작, 커밋, 롤백을 자동으로 처리하는 데 사용됩니다.
1. @Transactional의 주요 역할
1. 트랜잭션 시작:
• 메서드 호출 시 Spring은 트랜잭션을 시작합니다.
2. 트랜잭션 경계 설정:
• 메서드 실행이 끝날 때, 정상적으로 완료되면 커밋(Commit), 예외가 발생하면 **롤백(Rollback)**을 수행합니다.
3. 트랜잭션 전파 및 격리 수준 설정:
• 트랜잭션 간 상호작용 방식(전파)과 격리 수준을 정의할 수 있습니다.
2. @Transactional 주요 속성
(1) propagation (전파 레벨)
트랜잭션 전파는 현재 트랜잭션이 이미 존재하는 경우나 새로운 트랜잭션이 필요할 때 어떻게 동작할지를 정의합니다.
• REQUIRED (기본값):
• 이미 실행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여합니다.
• 없으면 새 트랜잭션을 생성합니다.
• REQUIRES_NEW:
• 항상 새 트랜잭션을 생성하며, 기존 트랜잭션은 일시 정지됩니다.
• SUPPORTS:
• 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행됩니다.
• NOT_SUPPORTED:
• 트랜잭션이 있으면 일시 정지하고 트랜잭션 없이 실행합니다.
• MANDATORY:
• 반드시 기존 트랜잭션에 참여해야 합니다. 트랜잭션이 없으면 예외를 발생시킵니다.
• NEVER:
• 트랜잭션이 있으면 예외를 발생시키고, 없으면 트랜잭션 없이 실행됩니다.
• NESTED:
• 기존 트랜잭션에 중첩된 트랜잭션을 생성합니다.
(2) isolation (격리 수준)
SQL 트랜잭션의 격리 수준을 설정합니다. 데이터베이스 간의 일관성과 동시성 문제를 해결하기 위해 사용됩니다.
• DEFAULT (기본값):
• 데이터베이스 드라이버의 기본 격리 수준을 사용합니다.
• READ_UNCOMMITTED:
• 다른 트랜잭션에서 커밋되지 않은 변경 사항도 읽을 수 있습니다. 성능은 좋지만 일관성은 낮습니다.
• READ_COMMITTED:
• 커밋된 데이터만 읽습니다. 대부분의 데이터베이스에서 기본값입니다.
• REPEATABLE_READ:
• 트랜잭션 동안 동일한 데이터를 읽으면 항상 같은 결과를 반환합니다.
• SERIALIZABLE:
• 가장 엄격한 격리 수준으로, 동시성은 떨어지지만 완벽한 일관성을 보장합니다.
(3) readOnly (읽기 전용)
• true: 트랜잭션이 데이터 읽기 작업만 수행하도록 설정합니다. 성능 최적화 및 의도 표시 용도로 사용됩니다.
• false (기본값): 읽기/쓰기 작업 모두 허용됩니다.
(4) rollbackFor 및 noRollbackFor
• 특정 예외가 발생했을 때 트랜잭션을 롤백하거나 롤백하지 않도록 설정합니다.
• rollbackFor: 롤백을 트리거할 예외 지정.
@Transactional(rollbackFor = CustomException.class)
• noRollbackFor: 롤백을 수행하지 않을 예외 지정.
@Transactional(noRollbackFor = IllegalArgumentException.class)
3. @Transactional의 적용 위치
• 클래스:
• 클래스 레벨에서 설정하면 모든 메서드에 트랜잭션이 적용됩니다.
@Transactional
public class OrderService {
public void createOrder() { ... }
public void cancelOrder() { ... }
}
• 메서드:
• 특정 메서드에만 트랜잭션을 적용할 수 있습니다.
public class OrderService {
@Transactional
public void createOrder() { ... }
}
4. @Transactional 동작 과정
1. AOP 적용:
• @Transactional은 AOP(Aspect-Oriented Programming)를 사용해 동작합니다.
• 트랜잭션이 시작되기 전에 프록시 객체가 동작하여 트랜잭션을 시작합니다.
• 트랜잭션 관리자는 프록시 객체를 통해 트랜잭션 시작, 커밋, 롤백을 자동으로 처리합니다. 이 과정은 개발자가 명시적으로 Connection이나 commit()을 호출하지 않아도 되도록 도와줍니다.
• 예를 들어, @Transactional이 선언된 메서드를 호출하면 Spring이 TransactionInterceptor를 통해 해당 메서드 실행 전후로 트랜잭션을 관리합니다.
2. Connection 관리:
• Spring은 트랜잭션 컨텍스트에서 데이터베이스 커넥션을 생성하거나 기존 커넥션을 재사용합니다.
3. 커밋 또는 롤백:
• 메서드가 정상적으로 종료되면 commit() 호출.
• 예외가 발생하면 rollback() 호출.
5. 예제 코드
기본 사용
@Service
public class OrderService {
@Transactional
public void placeOrder(Order order, Payment payment) {
orderRepository.save(order);
paymentRepository.process(payment);
// 모든 작업이 성공하면 커밋, 예외 발생 시 롤백
}
}
읽기 전용 트랜잭션
@Service
public class ReportService {
@Transactional(readOnly = true)
public List<Report> getReports() {
return reportRepository.findAll(); // 읽기 전용 트랜잭션
}
}
전파 속성 사용
@Service
public class NotificationService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotification(String message) {
// 항상 새로운 트랜잭션 생성
notificationRepository.save(message);
}
}
6. 주의사항
1. 프록시 기반 동작:
• 같은 클래스 내에서 호출된 메서드는 @Transactional이 적용되지 않을 수 있습니다.
@Service
public class OrderService {
@Transactional
public void placeOrder() {
savePayment(); // 같은 클래스 내 메서드 호출
}
@Transactional
public void savePayment() {
// 결제 처리 로직
}
}
실행 흐름:
1.1. 클라이언트 호출:
• 외부에서 placeOrder()를 호출 → 프록시 객체가 트랜잭션 시작.
• placeOrder() 메서드가 실행 중.
1.2. 내부 메서드 호출:
• placeOrder() 메서드 내에서 savePayment() 호출 → 프록시 객체를 거치지 않고 직접 호출됨.
• 결과적으로 savePayment()에 설정된 @Transactional은 적용되지 않음.
2. 체크 예외:
• Spring은 런타임 예외에 대해서만 기본적으로 롤백합니다.
• 체크 예외에서 롤백하려면 rollbackFor를 명시적으로 지정해야 합니다.
3. readOnly:
• readOnly = true 설정은 데이터베이스에 따라 성능 최적화 효과가 다를 수 있습니다.
4. 메서드 내부 호출:
• 같은 클래스에서 this.someTransactionalMethod()와 같이 호출하면 트랜잭션이 적용되지 않습니다. 이는 AOP 프록시가 동작하지 않기 때문입니다.
7. 최근 현업 사용
• Spring Data JPA와 함께 사용되는 경우가 많으며, 대부분의 데이터 처리에서 기본적으로 사용됩니다.
• 트랜잭션 전파, 격리 수준, 롤백 조건을 명시적으로 설정하여 복잡한 비즈니스 로직을 처리할 때도 널리 사용됩니다.
@Transactional은 데이터 일관성을 유지하고, 프로그래머가 복잡한 트랜잭션 처리를 간단히 처리할 수 있도록 해주는 중요한 도구입니다.
+ 추가질문
1. @Transactional은 AOP를 어떻게 사용하나요?
• Spring AOP 활용: @Transactional을 사용하면 Spring AOP를 통해 트랜잭션 관리가 구현됩니다.
• 프록시 기반 동작: Spring은 프록시를 생성하여 메서드 호출을 가로채고, 트랜잭션 시작과 종료를 관리합니다.
• AOP의 핵심 개념 사용:
• Advice: 트랜잭션 관리 로직은 AOP의 Advice로 구현되어 메서드 실행 전후에 적용됩니다.
• Pointcut: @Transactional이 적용된 메서드를 대상으로 하는 Pointcut이 정의됩니다.
• Advisor: Advice와 Pointcut을 결합하여 어떤 Advice를 어떤 Pointcut에 적용할지 결정합니다.
2. 프록시 사용이 AOP와 유사해서 AOP가 사용되는 건가요?
• 프록시를 통한 메서드 가로채기: 프록시를 사용하여 메서드 호출을 가로채고 부가 기능을 적용하는 방식은 AOP의 핵심 동작 방식입니다.
• AOP의 구현 방식 중 하나: Spring AOP는 프록시 기반으로 동작하며, 프록시를 통한 메서드 가로채기 자체가 AOP의 구현 방식입니다.
• 따라서, @Transactional을 사용할 때 프록시를 통해 메서드 호출을 가로채고 트랜잭션 관리 로직을 적용하는 것은 AOP를 사용하는 것입니다.
3. @Transactional 내부에 AOP 로직이 들어 있어서 AOP가 사용되는 건가요?
• @Transactional 자체가 어노테이션일 뿐: @Transactional 어노테이션은 메타데이터로서 해당 메서드나 클래스에 트랜잭션 관리가 필요하다는 정보를 제공합니다.
• Spring이 내부적으로 AOP 설정을 생성: Spring은 @Transactional을 감지하고, 내부적으로 AOP 설정(Advisor, Advice, Pointcut 등)을 생성합니다.
• 개발자가 AOP 설정을 명시적으로 작성하지 않아도: @Transactional을 사용하면 Spring이 자동으로 AOP 설정을 구성하므로, 개발자가 직접 AspectJ 어노테이션이나 AOP 설정을 작성할 필요가 없습니다.
• AspectJ를 직접 사용하지 않음: @Transactional을 사용할 때 AspectJ 프레임워크나 AspectJ 어노테이션을 내부적으로 사용하는 것은 아닙니다. 대신 Spring이 자체적으로 제공하는 AOP 기능을 활용합니다.
4. TransactionSynchronizationManager는 언제 사용되나요?
4.1. 트랜잭션의 시작과 종료 시점에 사용됩니다.
• 트랜잭션 시작: @Transactional 어노테이션이 적용된 메서드가 호출되면, Spring은 AOP를 통해 프록시를 생성하고, 해당 메서드 호출을 가로채어 트랜잭션을 시작합니다.
• 이때 **TransactionSynchronizationManager**는 현재 스레드에 트랜잭션 관련 정보를 저장하고 관리합니다. 트랜잭션 리소스(예: Connection 객체)를 바인딩하여 트랜잭션 내에서 동일한 리소스를 사용할 수 있게 합니다.
4.2. 트랜잭션 동기화 및 상태 관리를 위해 사용됩니다.
• 트랜잭션 내에서 실행되는 코드가 트랜잭션 상태나 리소스에 접근해야 할 때, **TransactionSynchronizationManager**를 통해 현재 트랜잭션의 정보를 가져올 수 있습니다.
• 예를 들어, 같은 스레드 내에서 여러 DAO나 서비스가 동일한 트랜잭션 리소스를 공유해야 할 때, **TransactionSynchronizationManager**가 이를 가능하게 합니다.
4.3. 트랜잭션 동기화 콜백을 등록하고 실행할 때 사용됩니다.
• 트랜잭션이 커밋되거나 롤백될 때 추가로 실행되어야 하는 작업(예: 캐시 갱신, 이벤트 발행 등)이 있다면, **TransactionSynchronizationManager**에 동기화 콜백을 등록하여 트랜잭션 종료 시점에 실행할 수 있습니다.