보통 서비스에 @Transactional 어노테이션을 이용하여 DB 에서 데이터를 가져오든, 변경하든 한다.
비즈니스 로직상, 하나의 기능을 구현함에 있어 DB 접근을 하여 처리하는 단위를 트랜잭션이라고 한다.
즉, 여러 DB 조작을 한 비즈니스 로직안 에서 수행할때 사용한다. 반대로 말하면, 단건일 경우는 @Transactional을 쓰지 않아도 된다.
@Transactional 어노테이션이 적용된 빈은, 프록시(https://dean83.tistory.com/342)를 생성하여 트랜잭션을 관리한다.
- 클래스 어노테이션으로 붙이는 경우
- 모든 public 메소드에 반영된다.
- 메소드 어노테이션으로 붙이는 경우
- 해당 메소드전용으로 트랜잭션이 반영된다.
- 클래스 어노테이션 < 메소드 어노테이션 이므로, 클래스 어노테이션에서 이용하고, 특정 메소드 전용 트랜잭션을 정의할 수 있다.
옵션값
- @Transactional( 옵션) 을 통해 옵션 설정이 가능하다
- propagation
- 이미 실행중인 트랜잭션이 있을 경우, 새로운 트랜잭션은 어떻게 처리할지 에 대한 옵션
- REQUIRED (기본): 기존 트랜잭션 있으면 합류, 없으면 새로 시작
- REQUIRES_NEW: 무조건 새로운 트랜잭션 시작 (기존 트랜잭션은 일시 중단)
- 하나의 트랜잭션 중, 로깅을 별도로 할때, 알람 전송 등 단독으로 쓸때 사용. 단, try catch를 통해 실패시 원래 트랜잭션에 영향이 가지 않도록 해야 함.
- SUPPORTS: 트랜잭션 있으면 합류, 없으면 그냥 트랜잭션 없이 실행
- MANDATORY: 무조건 기존 트랜잭션 필요 (없으면 예외 발생)
- NOT_SUPPORTED: 트랜잭션 없이 실행 (기존 트랜잭션은 중단됨)
- NEVER: 트랜잭션 있으면 예외 발생
- NESTED: 기존 트랜잭션 안에서 “중첩 트랜잭션” 시작 (DB savepoint 이용)
- 이미 실행중인 트랜잭션이 있을 경우, 새로운 트랜잭션은 어떻게 처리할지 에 대한 옵션
- isolation
- 여러 트랜잭션이 동시 실행될때 DB에서 데이터를 어떻게 격리할지 설정하는 옵션
- DEFAULT : DB 기본 격리 수준 사용 (기본값)
- READ_UNCOMMITTED : 커밋되지 않은 데이터도 읽음 (Dirty Read 허용) → 위험
- READ_COMMITTED : 커밋된 데이터만 읽음 (일반적인 DB에서 설정된 기본값)
- REPEATABLE_READ : 같은 트랜잭션 안에서는 같은 결과를 보장 (Non-repeatable read 방지)
- SERIALIZABLE : 가장 엄격, 완전 직렬화 (성능 저하 가능)
- 금융거래, 결제처리, 티케팅 등의 경우에 사용
- 여러 트랜잭션이 동시 실행될때 DB에서 데이터를 어떻게 격리할지 설정하는 옵션
- timeout
- 기본값은 -1로 무제한 이다. 타임아웃 되면 롤백 한다.
- readonly
- true로 설정시, 읽기전용으로 최적화 된다.
- propagation
코드 블록 내에서 트랜잭션을 관리하고 싶을 경우, 코딩을 통해 제어할 수 있다. 이 경우, 커밋, 롤백 등을 별도로 호출해야 하는데, 잘 쓰지는 않는다.
롤백 규칙
기본적으로 트랜잭션 도중 오류가 발생 할 경우, RuntimeException 및 이를 상속하는 Exception이 발생할 경우 롤백이 되며, 상위 부모 개념인 Exception 중 RuntimeException과 관련없는 오류 발생시에는 롤백이 되지 않는다. 즉, 커스텀 Exception 생성시 RuntimeException 을 상속하지 않은 경우는 롤백되지 않는다. 쉽게말해 Checked Exception은 롤백 안함, Unchecked Exception은 롤백 한다. (https://dean83.tistory.com/346)
첨부파일을 클라이언트로 부터 받았고, 해당 파일을 S3에 올리다가 오류가 발생하는 등의 경우에 롤백을 위해 쓸 수 있을거 같다.
@Transaction의 옵션으로, rollBackFor 과 noRollBackFor 를 이용해서 커스텀하게 설정 할 수 있다.
- rollBackFor
- 롤백이 되지 않는 exception도 롤백 하고 싶을때 설정
- noRollbackFor
- 롤백이 되는 exception을 롤백 안하고 싶을때 설정
@Transactional(
rollbackFor = 클래스명.class,
noRollbackFor = 클래스명.class
)
내부 메서드를 호출 할 경우 발생하는 문제
- 내부 메서드 호출 문제 (self invocation) 예를 보면
@Service
public class TestService
{
....
//transactional 메소드가 아님
public Entity create(...)
{
...비스니스 로직 수행
return this.createToDB(...);
}
@Transactional
public Entity createToDB(...)
{
...
}
...
}
- transactional 이 아닌 메서드에서 몇몇 비즈니스 로직 처리 -> transactional 이 있는 내부 메서드 호출을 통해 DB 접근 하려는 경우 발생한다.
- create 메소드는 트랜잭션이 아니므로 프록시를 이용하지 않는다.
- create 내부에서 createDB를 호출 하더라도, 이미 프록시가 아니기 때문에 트랜잭션이 동작하지 않는다.
- 따라서, 이 경우를 방지 하기 위해서 Service 클래스를 분리하여, 트랜잭션을 이용하는 메서드, 그렇지 않은 메서드로 나눠야 한다.
- 이 경우, 트랜잭션을 이용하지 않는 서비스에서 트랜잭션을 이용 하는 Service 주입시 프록시가 주입되어 트랜잭션이 동작하게 된다.
- ** 기타 방법으로는, 자기 자신을 인젝션 받는 방법 (단, lazy 인젝션을 받아야 함), 또는 AopContext.currentProxy 를 이용하여 프록시에 있는 메소드를 호출 하는 방법이 있으나, 둘 다 권장되지는 않는다. (한마디로 억지로 프록시를 이용하여 동작하는 방법)
'Backend > SpringBoot 이론 부분' 카테고리의 다른 글
| Filter 및 SecurityFilterChain (0) | 2025.12.15 |
|---|---|
| Spring boot의 요청 처리 흐름 (0) | 2025.12.15 |
| Entity 심화 (0) | 2025.09.30 |
| Proxy (0) | 2025.09.30 |
| Spring Data JPA (0) | 2025.09.29 |