@Transactional 이란?
@Transactional은 Spring에서 트랜잭션을 관리하기 위한 애너테이션입니다.
이 애너테이션을 사용하면 데이터베이스 작업을 하나의 논리적인 단위로 묶어서 처리할 수 있습니다.
즉, 여러 개의 SQL 쿼리가 모두 성공해야만 커밋(commit) 되고, 하나라도 실패하면 롤백(rollback) 됩니다.
@Transactional 사용 예제
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
private final AccountRepository accountRepository;
public AccountService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Transactional // 이 메서드는 트랜잭션 안에서 실행됨
public void transferMoney(Long fromAccountId, Long toAccountId, double amount) {
Account fromAccount = accountRepository.findById(fromAccountId).orElseThrow();
Account toAccount = accountRepository.findById(toAccountId).orElseThrow();
fromAccount.withdraw(amount);
toAccount.deposit(amount);
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
}
}
@Transactional이 적용된 메서드의 동작 원리
- transferMoney() 메서드가 실행됨
- 트랜잭션이 시작됨 (begin transaction)
- fromAccount.withdraw(amount), toAccount.deposit(amount) 실행
- accountRepository.save(fromAccount), accountRepository.save(toAccount) 실행
- 메서드 실행이 정상적으로 완료되면 트랜잭션을 커밋(commit)
- 실행 중 오류(예외)가 발생하면 트랜잭션을 롤백(rollback)
@Transactional의 주요 기능
(1) 트랜잭션 자동 롤백
@Transactional을 사용하면 기본적으로 런타임 예외 (RuntimeException 및 그 자식 클래스) 발생 시 자동으로 롤백됩니다
@Transactional
public void someMethod() {
saveToDatabase(); // 정상 실행됨
throw new RuntimeException("예외 발생!"); // 트랜잭션이 롤백됨
}
❗ 하지만, Checked Exception 발생 시 기본적으로 롤백되지 않음
- RuntimeException (ex: NullPointerException, IllegalArgumentException) → 자동 롤백
- Checked Exception (ex: IOException, SQLException) → 롤백 안 됨
✅ Checked Exception도 롤백시키려면?
@Transactional(rollbackFor = Exception.class)
public void someMethod() throws Exception {
saveToDatabase();
throw new Exception("Checked Exception 발생!"); // 롤백됨
}
(2) 트랜잭션 전파 옵션 (Propagation)
트랜잭션 전파 방식(Propagation)은 기존 트랜잭션이 있을 때 새 트랜잭션을 어떻게 처리할지를 결정합니다.
전파 유형 | 설명 |
REQUIRED (기본값) | 기존 트랜잭션이 있으면 참여, 없으면 새 트랜잭션 생성 |
REQUIRES_NEW | 항상 새로운 트랜잭션 생성 |
SUPPORTS | 트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 실행 |
NOT_SUPPORTED | 기존 트랜잭션을 사용하지 않고 실행 |
MANDATORY | 반드시 기존 트랜잭션이 있어야 하며, 없으면 예외 발생 |
NEVER | 트랜잭션이 있으면 예외 발생 |
NESTED | 중첩 트랜잭션을 생성 (부분 롤백 가능) |
@Transactional(propagation = Propagation.REQUIRES_NEW) // 항상 새로운 트랜잭션을 생성
public void newTransactionMethod() {
// 새로운 트랜잭션에서 실행됨
}
(3) 트랜잭션 격리 수준 (Isolation Level)
격리 수준(Isolation Level)은 여러 개의 트랜잭션이 동시에 실행될 때, 서로 간섭을 허용할지를 결정합니다.
격리 수준 | 설명 |
DEFAULT | DB 기본 설정을 따름 |
READ_UNCOMMITTED | 다른 트랜잭션의 변경 내용을 읽을 수 있음 (Dirty Read 허용) |
READ_COMMITTED | 다른 트랜잭션이 commit한 데이터만 읽을 수 있음 (Dirty Read 방지) |
REPEATABLE_READ | 같은 데이터를 반복해서 읽을 때 값이 변하지 않음 (Phantom Read 방지) |
SERIALIZABLE | 트랜잭션을 순차적으로 실행 (가장 엄격한 격리 수준) |
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void isolationMethod() {
// REPEATABLE_READ 격리 수준에서 실행됨
}
(4) 트랜잭션 읽기 전용 설정
읽기 전용 트랜잭션을 설정하면 데이터 변경이 불가능하며, 성능이 최적화됩니다.
@Transactional(readOnly = true)
public List<Account> getAllAccounts() {
return accountRepository.findAll();
}
✅ 읽기 전용으로 설정하면 데이터베이스에 변경이 발생하는 INSERT, UPDATE, DELETE가 차단됨
✅ 성능이 향상됨 (MySQL에서는 InnoDB 엔진이 잠금(lock) 비용을 줄일 수 있음)
@Transactional 사용 시 주의할 점
(1) private 메서드에는 적용되지 않음
- @Transactional은 Spring AOP(Aspect-Oriented Programming) 기반으로 동작합니다.
- 따라서 Spring Proxy가 감지할 수 없는 private 메서드에서는 동작하지 않음.
@Transactional
private void privateMethod() { // 트랜잭션 적용되지 않음!
// 트랜잭션이 관리되지 않음
}
✅ 해결 방법: public 메서드로 변경
(2) 같은 클래스의 메서드에서 호출하면 적용되지 않음
- 자기 자신의 메서드를 호출하면 @Transactional이 적용되지 않음 (Spring Proxy를 거치지 않음).
@Service
public class MyService {
@Transactional
public void outerMethod() {
innerMethod(); // 트랜잭션이 적용되지 않음!
}
@Transactional
public void innerMethod() {
// 트랜잭션을 적용하려 했으나 실패함
}
}
✅ 해결 방법: 다른 Bean에서 호출하도록 변경
@Service
public class AnotherService {
private final MyService myService;
@Autowired
public AnotherService(MyService myService) {
this.myService = myService;
}
public void execute() {
myService.innerMethod(); // 정상적으로 트랜잭션 적용됨
}
}
결론
특징 |
설명 |
트랜잭션 자동 롤백 | RuntimeException 발생 시 자동 롤백 |
트랜잭션 전파(Propagation) | 기존 트랜잭션이 있으면 재사용 또는 새 트랜잭션 생성 |
격리 수준(Isolation) | 트랜잭션 간 간섭을 방지하는 설정 가능 |
읽기 전용 트랜잭션 | readOnly = true로 성능 최적화 |
적용되지 않는 경우 | private 메서드, 같은 클래스 내부 호출 |
Spring의 @Transactional을 사용하면 데이터 정합성을 보장하고 트랜잭션 관리를 자동화할 수 있습니다.
비즈니스 로직이 DB 작업을 포함할 경우, @Transactional을 적극적으로 활용하는 것이 좋습니다.
참고 :
Proxy형태로 동작하는 JPA @Transactional
[Spring] @Transactional 잘 사용해보기
[Spring] 📚 @Transactional 이해하기
[Spring & Java] 🚀 재고시스템으로 알아보는 동시성이슈 해결방법
[spring] private와 inner method에서의 spring trasaction 동작
'개발 > Spring' 카테고리의 다른 글
[Spring] h2 DB 연결하고 JPA 사용하기 (0) | 2023.04.27 |
---|---|
[Spring] 스프링시큐리티(Spring Security) 개념 (0) | 2023.04.26 |
[Spring] JPA와 ORM (0) | 2023.03.22 |
[Spring] spring 기초 (0) | 2023.03.09 |
[Spring] 어노테이션 모음집 (0) | 2023.03.08 |