@startuml skinparam defaultFontName "NanumGothic" package org.springframwork.transaction { interface TransactionManager interface PlatformTransactionManager interface ReactiveTransactionManager package org.springframwork.transaction.support { interface ResourceTransactionManager abstract class AbstractPlatformTransactionManager abstract class AbstractReactiveTransactionManager } package org.springframework.transaction.annotation { annotation Transactional } TransactionManager <|-- PlatformTransactionManager TransactionManager <|-- ReactiveTransactionManager PlatformTransactionManager <|-- ResourceTransactionManager PlatformTransactionManager <|-- AbstractPlatformTransactionManager } package org.springframework.jdbc { package org.springframework.jdbc.datasource { class DataSourceTransactionManager } package org.springfremework.jdbc.support { class JdbcTransactionManager } } package org.springframework.orm.jpa { class JpaTransactionManager } AbstractPlatformTransactionManager <|-- DataSourceTransactionManager ResourceTransactionManager <|-- DataSourceTransactionManager DataSourceTransactionManager <|-- JdbcTransactionManager AbstractPlatformTransactionManager <|-- JpaTransactionManager ResourceTransactionManager <|-- JpaTransactionManager @enduml
-
PlatformTransactionManager
: 트랜잭션의 경계를 지정하는데 사용.commit
,rollback
인터페이스 -
https://godekdls.github.io/Spring%20Data%20Access/transactionmanagement/
격리 수준(Isolation)
-
동시에 여러 트랜잭션이 처리될 때, 트랜잭션끼리 얼마나 서로 고립되어 있는지는 나타내는 것
-
특정 트랜잭션이 다른 트랜잭션에 변경한 데이터를 볼 수 있도록 허용할지 말지를 결정하는 것
ANSI/ISO SQL Standard (SQL192)
Note
|
ANSI SQL
DBMS(Oracle, My-SQL, DB2 등등)들에서 각기 다른 SQL를 사용하므로, 미국 표준 협회American National Standards Institute에서 이를 표준화하여 표준 SQL문을 정립 시켜 놓은 것이다. |
Level 0. Read Uncommitted
1. A 트랜잭션에서 ID가 1인 사용자 나이를 20에서 21로 변경함
2. 아직 커밋하지 않음
3. B 트랜잭션에서 ID가 1인 사용자의 나이를 조회함
4. 21이 조회됨 (1)
5. A 트랜잭션에서 문제가 발생하여 롤백함
6. B 트랜잭션은 ID가 1인 사용자의 나이가 여전히 21이라고 생각하고 로직 수행함
-
이를 Dirty Read라고 함
-
이 경우 커밋되지 않은 데이터를 다른 트랜잭션이 조회하여 사용함
-
트랜잭션에서 처리중인/아직 커밋되지 않은 데이터를 다른 트랜잭션이 읽는 것을 허용
-
Dirty Read, Non-Repeatable Read, Phantom Read 현상 발생
Note
|
Dirty Read
|
Note
|
Non-Repeatable Read
|
Note
|
Phantom Read
한 트랜잭션 안에서 일정범위의 레코드를 두번 이상 읽을 때, 첫번째 쿼리에서 없던 유령 레코드가 두번째 쿼리에서 나타나는 현상 |
Level 1. Read Committed
1. B 트랜잭션에서 ID가 1인 사용자 나이 조회
2. 20이 조회됨
3. A 트랜잭션에서 ID가 1인 사용자 나이를 20에서 21로 변경하고 커밋함
4. B 트랜잭션에서 ID가 1dl 사용자 나이 재조회
5. 21이 조회됨 (1)
-
하나의 트랜잭션에서 똑같은 쿼리를 수행했을 때 같은 결과를 반환해야한다는 Repeatable Read 정합성이 어긋남
-
커밋되어야만 다른 트랜잭션에서 조회 가능함
Level 2. Repeatable Read
-
트랜잭션이 시작되지 전에 커밋된 내용에 대해서만 조회할 수 있는 격리 수준
-
Non-Repeatable Read 부정합이 발생하지 않음
-
자신의 트랜잭션보다 낮은 트랜잭션 번호에 변경된(커밋된) 것만 보게 되는 것
-
트랜잭션이 길어질수록 버전을 유지해야하는 단점 존재
Level 3. Serializable Read
-
가장 단순하고 엄격한 격리수준
-
읽기 작업에도 락 설정
-
동시 처리 능력이 다른 격리 수준보다 떨어짐
전파(Propagation)
- REQUIRED
-
기본 속성. 부모 트랜잭션이 있으면 참여하고 아니면 신규로 진행한다. 예외가 발생하면 롤백되고 호출한 곳에도 롤백이 전파된다.
- REQUIRED_NEW
-
진행중인 트랜잭션이 있으면 잠시 보류하고, 새로운 트랜잭션 시작한다. 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성된다. 각 트랜잭션이 독립적으로 동작한다. 트랜잭션간 예외로 인한 롤백이 전파되지 않는다.
- NESTED
-
이미 진행중인 트랜잭션이 있으면 중첩 트랜잭션을 시작한다. 둘러쌓인 트랜잭션이 없을 경우 REQUIRED와 동일하다. DB가
SAVEPOINT
기능을 지원한다면 지정한 시점까지 부분 롤백이 가능하다. - MANDATORY
-
부모 트랜잭션 내에서 실행하며, 부모 트랜잭션이 없을 경우 예외가 발생한다.
- SUPPORT
-
부모 트랜잭션 내에서 실행되며, 부모 트랜잭션이 없을 경우 non-transactionally로 실행된다.
- NOT_SUPPORT
-
non-transactionally로 실행되며, 부모 트랜잭션이 존재하면 일시 정지한다.
- NEVER
-
non-transactional로 실행되며, 부모 트랜잭션이 있을 경우 예외가 발생한다.
kotlin
Example
@Service
fun UserService(
transactionManager: PlatformTransactionManager,
) {
fun execute() {
TransactionTemplate(transactionManager)
.apply {
isolationLevel = TransactionDefinition.ISOLATION_DEFAULT
propagationBehavior = TransactionDefinition.PROPAGATION_MANDATORY
timeout = 10_000
isReadOnly = true
}
.execute {
// 트랜잭션 범위
}
}
// TODO: open class가 되어야 함
@Transactional
open fun executeWithAnnotation() {
// 트랜잭션 범위
}
}