Spring Framework에서 @Transactional 애노테이션은 데이터베이스 트랜잭션을 선언적으로 관리할 수 있게 해주는 매우 강력한 기능입니다. 그러나 많은 개발자들이 "도대체 언제 commit이 되는 거지?", "왜 내가 기대한 대로 트랜잭션이 작동하지 않을까?" 라는 의문을 품곤 합니다. 이 글에서는 @Transactional을 사용할 때 실제로 commit이 발생하는 시점과 주의할 점, 그리고 내부적으로 어떤 원리로 작동하는지에 대해 자세히 다뤄보겠습니다.
1. @Transactional이란?
@Transactional은 메서드나 클래스에 붙여 트랜잭션 경계를 설정합니다. Spring은 이 애노테이션을 기반으로 AOP(Aspect Oriented Programming)를 적용하여 트랜잭션을 시작하고, 정상 종료 시 commit, 예외 발생 시 rollback 처리합니다.
@Transactional
public void saveUser(User user) {
userRepository.save(user); // 트랜잭션 안에서 실행됨
}
2. 언제 commit이 되는가?
✅ commit은 메서드가 정상적으로 종료될 때 발생합니다.
- @Transactional이 붙은 메서드가 호출됨
- Spring AOP가 트랜잭션 시작
- 메서드 실행
- 예외가 발생하지 않고 정상적으로 리턴되면 → commit
- RuntimeException 혹은 Error가 발생하면 → rollback
즉, commit은 @Transactional 메서드가 정상적으로 종료될 때 자동으로 수행됩니다. 명시적으로 커밋을 호출할 필요는 없습니다.
3. 트랜잭션이 commit되지 않는 이유들
🔁 1. 내부 메서드 호출 (self-invocation)
@Transactional
public void outer() {
inner(); // @Transactional 적용이 안됨
}
@Transactional
public void inner() {
// 트랜잭션 미작동
}
Spring AOP는 프록시 기반이므로, 같은 클래스 안에서 메서드를 호출하면 트랜잭션이 적용되지 않습니다. 이럴 땐 구조를 리팩토링해서 외부 Bean에서 호출되도록 해야 합니다.
⚠️ 2. 체크 예외(Checked Exception)
@Transactional
public void doSomething() throws IOException {
throw new IOException("Checked 예외");
}
Spring의 기본 정책은 체크 예외 발생 시 rollback하지 않고 commit을 수행합니다. rollback을 원한다면 rollbackFor 속성을 설정해야 합니다.
@Transactional(rollbackFor = IOException.class)
public void doSomething() throws IOException {
throw new IOException("Checked 예외");
}
⏱️ 3. 트랜잭션 범위 외부에서 실행되는 코드
트랜잭션은 @Transactional이 적용된 메서드의 실행 범위에서만 유효합니다. 트랜잭션이 종료된 이후에 실행되는 작업은 별도의 트랜잭션이 필요합니다.
4. 트랜잭션 커밋을 강제로 제어하려면?
기본적으로는 Spring에게 맡기는 것이 이상적이지만, 특정한 경우 직접 제어할 수도 있습니다.
✅ TransactionTemplate 사용
transactionTemplate.executeWithoutResult(status -> {
try {
userRepository.save(user);
} catch (Exception e) {
status.setRollbackOnly(); // 수동 롤백
}
});
✅ PlatformTransactionManager 직접 사용
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
userRepository.save(user);
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
}
이런 방식은 주로 외부 API 연동 시 커밋 타이밍을 세밀하게 조정해야 할 때 사용됩니다.
5. 트랜잭션 커밋 시 주의할 점
- 커밋 타이밍에 따라 DB 락 대기 시간이 늘어날 수 있으므로, 트랜잭션 안에는 반드시 최소한의 로직만 넣는 것이 좋습니다.
- 커밋 이후에는 트랜잭션 컨텍스트가 닫히므로 Lazy 로딩된 객체 접근 등은 불가능합니다.
- 대량 처리의 경우에는 트랜잭션을 나눠 처리하여 OutOfMemory를 방지하세요.
결론
@Transactional은 매우 강력한 기능이지만, 작동 방식과 commit 타이밍을 정확히 이해하지 않으면 예기치 않은 결과를 초래할 수 있습니다. 핵심은 다음과 같습니다.
- 트랜잭션은 메서드가 정상 종료되면 자동으로 commit된다.
- 내부 호출, 체크 예외, 범위 외 실행은 예상과 다르게 동작할 수 있다.
- 필요할 경우 TransactionTemplate이나 트랜잭션 매니저로 수동 제어가 가능하다.
Spring 트랜잭션의 작동 방식을 정확히 이해하면, 안정적이고 성능 좋은 시스템을 설계하는 데 큰 도움이 됩니다.
'프로그래밍 > Java' 카테고리의 다른 글
파일 업로드를 위한 Pre-signed URL 사용법 (Java 예제 포함) (2) | 2025.05.16 |
---|---|
Spring Cloud에서 Resilience4j로 Circuit Breaker 구현하기 (0) | 2025.05.11 |
REST API 버전 관리 – URL 방식 vs 헤더 방식 비교 (0) | 2025.05.08 |
API 설계 시 버전을 명시하는 방법 (0) | 2025.05.08 |
빌더 (Builder) 클래스란? (2) | 2025.03.02 |