@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

격리 수준(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이라고 생각하고 로직 수행함
  1. 이를 Dirty Read라고 함

  • 이 경우 커밋되지 않은 데이터를 다른 트랜잭션이 조회하여 사용함

  • 트랜잭션에서 처리중인/아직 커밋되지 않은 데이터를 다른 트랜잭션이 읽는 것을 허용

  • Dirty Read, Non-Repeatable Read, Phantom Read 현상 발생

Note
Dirty Read
  • 커밋되지 않은 데이터 읽기

  • 더티 페이지란 데이터 캐시에는 변경 되었지만, 아직 디스크에는 변경되지 않은 데이터(페이지)

  • Uncommitted Dependency

Note
Non-Repeatable Read
  • 최초에 읽기 작업 후, 다른 트랜잭션이 데이터를 변경시키고, 그 다음 읽기 작업이 변경된 사항을 읽어들여 최초의 읽기와 두 번째 읽기 작업의 결과가 불일치하는 경우

  • Inconsistent Analysis

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)
  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() {
    // 트랜잭션 범위
  }
}