Spring Transaction Management
Reference
https://stackoverflow.com/questions/8490852/spring-transactional-isolation-propagation
https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-event
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/TransactionDefinition.html
Spring 5 Reciepes - Chapter 10. Spring Transaction Management
트랜잭션 모델
Global Transaction
RDB, messege queue 등 다른 datasource의 트랜잭션을 동시에 관리하는 방법.
기본적으로 JTA로 관리하지만 JNDI가 필요하므로 재사용성에 제약이 생기며 서버 환경에서만 작동한다는 문제점이 있다.
Spring에서는 JtaTransactionManager
로 JTA를 사용할 수 있다.
특정 datasource를 대상으로 작동하는것이 아니기 때문에 제공해주지 않아도 된다.
Local Transaction
datasource 한개를 대상으로 트랜잭션 관리하는 방법. Spring data가 기본적으로 채택하는 트랜잭션 관리 방법이다.
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
@Transactional
@Transactional
어노테이션을 사용하려면 꼭 접근자가 public인 메소드에 선언해야한다.
protected나, private, default면 에러는 안나지만 적용이 안된다.
꼭 필요하면 AspectJ의 @EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
와
@EnableLoadTimeWeaving
참고
read-only
읽기 전용으로 트랜잭션을 획득해 해당 트랜잭션을 열고있는 동안은 수정이 불가능하다.
timeout
해당 트랜잭션의 실행 시간이 timeout을 초과할 시
기본적으로 타임아웃이 존재하지 않아서 따로 설정해주지 않을시 데드락이 발생할 수도 있다.
uncheckedException(보통 RuntimeException
)이 발생할때만 자동으로 롤백하며
그 외 checkedException(SqlException
)등은 롤백하지 않는다.
Propagation
각 트랜잭션 사이의 관계(공존 여부, 우선순위 등)를 정의
CONSTANT | Meaning | Remarks |
---|---|---|
PROPAGATION_REQUIRED | 현재 트랜잭션을 사용하되, 없으면 생성한다 | default |
PROPAGATION_MANDATORY | 현재 있는 트랜잭션을 무조건 사용한다. 없으면 Exception을 던진다 | |
PROPAGATION_REQUIRES_NEW | 기존 트랜잭션 존재 여부와 상관없이 무조건 새로 생성한다. | 기존 트랜잭션은 suspend됨 |
PROPAGATION_NOT_SUPPORTED | 트랜잭션을 사용하지 않는다. | |
PROPAGATION_NEVER | 트랜잭션을 사용하지 않는다. 기존 트랜잭션이 있으면 Exception을 던진다 | |
PROPAGATION_NESTED | 물리적 트랜잭션 1개 안에 여러개의 논리적(서브) 트랜잭션을 만든다 | 물리적 트랜잭션을 기준으로 한개로 처리, 없으면 PROPAGATION_REQUIRED 와 같이 작동 |
Isolation
여러개의 트랜잭션이 동시에 같은 dataset을 다를때 어떻게 할것인지 설정해주는 옵션.
데이터를 읽을 때 아래와 같은 동시성 문제를 방지하려면 적절하게 설정해줘야 한다.
Isolation Level Mode | Read | Insert | Update | Lock Scope |
---|---|---|---|---|
READ_UNCOMMITTED | uncommitted data | Allowed | Allowed | No Lock |
READ_COMMITTED (Default) | committed data | Allowed | Allowed | Lock on Committed data |
REPEATABLE_READ | committed data | Allowed | Not Allowed | Lock on block of table |
SERIALIZABLE | committed data | Not Allowed | Not Allowed | Lock on full table |
두개의 트랜잭션 T1
, T2
가 같은 테이블에서 작업한다고 가정하자.
Dirty read
- T1이 첫번째 row를 업데이트
- T2가 T1이 커밋하기 전에 해당 row를 읽음.
- T1이 롤백했을 경우 T2는 실제로 존재하지않는 로우를 읽어오게 됨.
non-repeatable read
- T1내에서 첫번째 row를 읽음.
- T2내에서 첫번째 로우를 삭제하거나 업데이트한 뒤 커밋
- T1내에서 다시 첫번째 row를 읽었을 때 첫번째와 다른 결과를 보게 됨 됨.
Phantom Reads
- T1내에서 특정 조건을 만족하는 row들을 조회함
- T2에서 위의 조건을 만족하는 row를 insert
- T1내에서 처음 쿼리를 다시 실행했을 때 이전에는 존재하지 않았던 레코드가 나오게 됨.
CONSTANT | MEANING | REMARKS |
---|---|---|
ISOLATION_DEFAULT | datastore의 기본 설정을 따른다 | default |
ISOLATION_READ_UNCOMMITTED | 아직 커밋되지 않은 데이터를 읽을 수 있다. | DIRTY-READ, NON-REPEATABLE-READ, PHANTOM-READ는 해결 안됨 |
ISOLATION_READ_COMMITTED | 커밋된 데이터만 읽을 수 있다. | NON-REPEATABLE-READ, PHANTOM-READ는 해결 안됨 |
ISOLATION_REPEATABLE_READ | 값을 읽을 때 해당 값의 데이트를 허용하지 않는다.(block lock) | PHANTOM-READ는 해결 안됨(로우를 삽입하고 있을때 생기는 문제라서) |
ISOLATION_SERIALIZABLE | 한 트랜잭션이 table lock을 걸어버린다. | 위의 문제는 해결되지만 성능에 문제가 생긴다. |
TransactionManager 여러개 사용하기
한 인스턴스에서 여러개의 트랜잭션 매니저를 사용해야 하는 경우 (데이터 소스 여러개를 관리하는 경우)
각 datasource의 DataSourceTransactionManager bean name을 명시해주면 된다.
두 트랜잭션을 동시에 관리하려면
JtaTransactionManager
를 사용해야 한다
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
}