1. 클래스 레벨 @Transactional
@Service
@Transactional(readOnly = true) // 클래스 레벨
public class AccountService {
public List<AccountDto> list() { }
public void findById(Long id) { }
@Transactional // 메서드 레벨
public void transfer() { }
}
특징
- 클래스 레벨에 붙이면 → 해당 클래스의 모든 public 메서드에 트랜잭션이 적용됨
- 클래스 레벨에 설정된 속성 (readOnly = true)이 기본값이 됨
- 메서드 별도로 @Transactional을 붙이면 그 메서드에는 메서드 설정이 우선 적용된다.
위 코드에서는
- list() → readOnly = true 트랜잭션
- findById() → readOnly = true 트랜잭션
- transfer() → 일반 트랜잭션 (메서드 설정이 덮어씀)
2. 메서드 레벨 @Transactional
@Service
public class AccountService {
@Transactional(readOnly = true)
public List<AccountDto> list() { }
@Transactional
public void transfer() { }
}
특징
- 각 메서드에 필요한 트랜잭션 정책을 직접 지정
- 클래스에 아무것도 없으니 모든 메서든느 기본적으로 트랜잭션 없음
- 개별 메서드 마다 붙여야해서 코드가 장황해질 수 있다
성능 영향
- 트랜잭션을 켜는 순간 스프링/JPA/DB 레벨에서 여러 추가 비용이 들어간다.
1. DB 커넥션 점유
- 트랜잭션은 DB 커넥션을 풀에서 빌려와서 잡고 있어야한다.
- 트랜잭션 범위가 길면 → 커넥션을 오래 점유 → 다른 요청이 기다리게 됨(병목)
2. 락(lock) 유지
- 쓰기 트랜잭션은 DB에 레코드/테이블 락을 잡을 수 있다.
- 길게 잡으면 다른 트랜잭션이 기다리거나, 교착상태(deadlock) 위험이 커짐
3. 영속성 컨텍스트 관리 비용
- JPA는 트랜잭션 동안 엔티티를 1차 캐시에 보관 + 더티 체킹(변경 감지)
- 범위가 넓어질수록 엔티티가 많아지고 → 메모리/체킹 비용이 커짐
4. 커밋/롤백 로그 비용
- DB는 트랜잭션을 안전하게 커밋하기 위해 로그(WAL/redo log)를 남긴다
- 트랜잭션이 길수록 로그가 많아지고 장애 시 복구 시간도 길어짐
트랜범위를 최소화
핵심은 “트랜잭션 안에는 꼭 DB 변경과 무결성 보장에 필요한 최소 코드만 두자” 이다.
1. 성능 최적화
- 짧게 잡으면 커넥션 점유 시간 ↓ → 동시 처리 성능 ↑
- 락 점유 시간 ↓→ 교착 상태/대기 시간 ↓
2. 안정성 확보
- 외부 API 호출, 파일 I/O 같은 “느리고 실패할 수 있는 것”이 트랜잭션 안에 있으면 위험하다.
- API 응답이 늦어지면 → DB 커넥션과 락을 계속 점유
- 실패 시 불필요하게 롤백 범위가 커짐
3. 가독성과 의도 명확화
- @Transactional이 붙은 부분만 보면 “아, 여기서 DB 무결성을 보장하는 중요한 로직이 실행되는구나”를 알 수 있다
- 트랜잭션 범위가 크면 어디서 문제가 생겼는지 파악하기도 힘들어짐
예시
@Service
public class AccountService {
private final AccountRepository accountRepository;
// 조회는 readOnly
@Transactional(readOnly = true) // 기본 조회용
public List<AccountDto> list() {
return accountRepository.findAll()
.stream()
.map(AccountDto::fromEntity)
.toList();
}
// 쓰기만 트랜잭션 on
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
Account a = accountRepository.findByOwner(from).orElseThrow();
Account b = accountRepository.findByOwner(to).orElseThrow();
a.withdraw(amount);
b.deposit(amount);
// 커밋 시점에 UPDATE SQL
}
}'개발 지식 > KSUG' 카테고리의 다른 글
| [KSUG Spring Boot Study] Isolation Level (1) | 2025.08.28 |
|---|---|
| [KSUG Spring Boot Study] Propagation (1) | 2025.08.28 |
| [KSUG Spring Boot Study] 정의(ACID) (3) | 2025.08.28 |
| [KSUG Spring Boot Study] DAO 연동을 위한 컨트롤러와 서비스 설계 (1) | 2025.08.17 |
| [KSUG Spring Boot Study] DAO 설계 (1) | 2025.08.17 |