들어가며...
Spring Framework를 이용해서 개발을 하다보면 사용하게 되는 @Transactional 어노테이션을 보다가 문득 내가 사용해본 옵션 말고
어떠한 옵션들이 또 있을까? 궁금해져서 라이브러리 내부를 따라가보았다.
내부에 있는 옵션에 대한 설명들을 보면서 어떤 옵션들을 제공하는지 알아보자.
package org.springframework.transaction.annotation;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* The transaction propagation type.
* <p>Defaults to {@link Propagation#REQUIRED}.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
*/
Propagation propagation() default Propagation.REQUIRED;
...
트랜잭션의 주요 특성 (ACID)
트랜잭션에는 ACID라고 하는 4가지 특성이 있는데 이는 다음과 같다.
- Atomicity (원자성): 트랜잭션 내의 모든 작업은 하나의 작업처럼 처리되며, 만약 트랜잭션 중 하나라도 실패하면 전체 트랜잭션이 롤백(rollback)
- Consistency (일관성): 트랜잭션이 시작되기 전과 종료 후, 데이터베이스는 항상 일관된 상태를 유지해야한다.
- Isolation (격리성): 트랜잭션이 다른 트랜잭션에 영향을 미치지 않도록 격리한다. 격리 수준에 따라 다른 트랜잭션의 작업이 현재 트랜잭션에 미치는 영향을 조정할 수 있다.
- Durability (지속성): 트랜잭션이 완료되면 그 결과는 영구적으로 저장된다. 즉, 시스템 오류가 발생하더라도 커밋된 트랜잭션의 결과는 사라지지 않는다.
위의 특성들을 상황에 따라 어떻게 처리할 것인가에 대해 다음과 같은 옵션들을 제공한다.
제공되는 옵션들
@Transactional 에너는 다음과 같은 옵션들을 제공한다.
- propagation
- isolation
- rollbackFor
- noRollbackFor
- readOnly
- timeout
- value
이중에서 실제로 현업에서 사용해본 옵션은 readOnly 말고는 딱히 사용 해본적은 없다.
그러나 설명들을 보면서 나중에 기회가 된다면 단순하게 풀어낼 수 없는 로직적인 내용들을 풀어내거나 때론 예상하지 못한 이유로 쓸일이 있을수도 있을 것 같다.
propagation
현재 진행중인 트랜잭션 (부모 트랜잭션) 이 존재할 때 새로운 트랜잭션 메소드를 호출하는 경우 어떻게 전파될지를 결정하는 옵션으로 기본 값 REQUIRED
- REQUIRED (기본값)
기본 값으로 기존 트랜잭션(부모)이 있으면 참여하고, 없으면 새 트랜잭션을 생성 - REQUIRES_NEW
기존 트랜잭션(부모)이 있어도 무조건 새로운 트랜잭션을 생성 (기존 트랜잭션은 잠시 보류됨) - SUPPORTS
기존 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행 (non-transactional) - MANDATORY
반드시 기존 트랜잭션이 있어야 하며, 없으면 예외 발생 (TransactionRequiredException) - NEVER
기존 트랜잭션이 있으면 예외 발생 (트랜잭션 없이 실행 - non-transactional) - NOT_SUPPORTED
non-transactional(트랜잭션 없이)으로 시작하며 기존 트랜잭션(부모)이 있으면 보류 - NESTED
기존 트랜잭션이 있으면 별개의 중첩된 트랜잭션을 생성하여 실행
부모 트랜잭션의 커밋과 롤백에는 영향을 받지만 자신의 커밋과 롤백은 부모 트랜잭션에게 영향을 주지 않음
부모 트랜잭션이 없는 경우 새로운 트랜잭션을 만듦
isolation
트랜잭션이 다른 트랜잭션과 어떻게 격리될지를 결정하는 옵션으로, 기본값 DEFAULT
- DEFAULT (기본값)
데이터베이스의 기본 격리 수준을 따른다. - READ_UNCOMMITTED
다른 트랜잭션에서 commit되지 않은 데이터도 읽을 수 있다. (Dirty Read 가능) - READ_COMMITTED
다른 트랜잭션에서 commit된 데이터만 읽을 수 있다. (Dirty Read 방지)
A 트랜잭션이 데이터를 변경해도 커밋하기 전이라면 B 트랜잭션은 변경되지 전의 데이터를 조회한다. - REPEATABLE_READ
같은 트랜잭션 내에서 동일한 데이터를 여러 번 읽어도 결과가 변하지 않는다. (Phantom Read 가능)
다른 트랜잭션에서 수행한 작업에 의해 안보였던 데이터가 보이는 현상이 발생할 수 있다.
격리 수준은 조회한 데이터에 대해서만 Shared Lock이 걸리기 때문에 다른 트랜잭션이 새로운 데이터를 추가할 수 있다. - SERIALIZABLE
가장 엄격한 격리 수준으로, 트랜잭션 간 충돌을 방지하기 위해 데이터에 대한 순차적 접근을 보장한다. (성능 저하 가능)
가장 안전하지만 성능 저하가 발생하기 때문에 극도의 안정성을 필요로 하지 않으면 자주 사용되지는 않는다.
rollbackFor
특정 예외가 발생하면 롤백하도록 설정하는 옵션
@Transactional(rollbackFor = RuntimeException.class)
- 사용할 때 @Transactional(rollbackFor = RuntimeException.class) 처럼 특정 Exception Type 발생시 커밋하지 않고 롤백 되도록 설정할 수 있다.
- 기본 값으로 RuntimeException과 Error 가 포함되어 있다. 중요한 점은 다른 Exception을 추가해도 RuntimeException과 Error에 대해 동작한다.
noRollbackFor
특정 예외가 발생해도 롤백하지 않도록 설정하는 옵션
@Transactional(noRollbackFor = {YoungBlueException.class})
예를 들면 사용자가 직접 정의한 YoungBlue라는 Exception이 있다고 할 때, 해당 Exception이 발생하면 롤백하지 않는다.
readOnly
트랜잭션을 읽기 전용으로 설정하는 옵션
@Transactional(readOnly = true)
- 기본값 false 이고, true로 설정하는 경우 트랜잭션을 읽기 전용으로 변경한다.
- JPA에서 Dirty Checking을 무시하는 상태가 되며, Entity의 상태 변경에 따른 update를 무시하게 된다. (조회용 메소드에 사용)
- 이에 따라 성능이 향상될 수 있다.
timeout
트랜잭션이 수행될 최대 시간을 설정하는 옵션
@Transactional(timeout = 10)
- 기본값은 -1로 이 경우 timeout을 설정하지 않는 것과 같다.
- 지정한 시간 내에 해당 메소드 수행이 완료되지 않을 경우 JpaSystemException을 발생 시킨다.
value
Spring의 여러 트랜잭션 관리자가 있을 경우 특정 트랜잭션 관리자를 지정할 때 사용
@Transactional(value = "transactionManagerName")
정리
Option Name | Description | Default |
propagation | 트랜잭션 전파 방식 | REQUIRED |
isolation | 트랜잭션 격리 수준 | DEFAULT |
rollbackFor | 롤백할 예외 지정 | 없음 |
noRollbackFor | 롤백하지 않을 예외 지정 | 없음 |
readOnly | 읽기 전용 트랜잭션 | false |
timeout | 트랜잭션 최대 실행 시간 (초) | -1 (무제한) |
value | 특정 트랜잭션 매니저 지정 | 없음 |
'백엔드 > Spring' 카테고리의 다른 글
Batch Insert를 통한 속도 개선기 (0) | 2025.01.08 |
---|---|
JpaRepository save와 saveAll의 차이 그리고 Batch Insert (0) | 2025.01.05 |
[JPA] queryDsl cannot find symbol Q class 오류 (0) | 2023.05.11 |
[JPA] but parameter 'Optional[xx]' not found in annotated query. (2) | 2023.05.11 |