~/tech-blog/posts/transactional-basics — zsh

@Transactional 이란? 트랜잭션 성질

트랜잭션이란?

💡 트랜잭션(Transaction)은 데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 모두 수행되어야 할 일련의 연산들을 의미합니다!

모든 트랜잭션이 같은 방식으로 작동하지는 않습니다. 전체가 같이 성공하거나 실패하는 하나의 작업으로 묶인다는 것은 변치 않지만, 세밀히 따지면 차이점이 존재합니다.

트랜잭션이 끝나면 Commit 또는 RollBack 되어야 합니다.

트랜잭션 성질

  • Atomicity(원자성)
    • 하나의 트랜잭션이 더 작게 나눌 수 없는 최소의 단위라는 뜻입니다. 트랜잭션이 모두 반영되거나, 아니면 전혀 반영되지 않아야 하는 특징을 나타냅니다.
  • Consistency(일관성)
    • 일관성은 트랜잭션이 완료된 결괏값이 일관적인 데이터베이스 상태를 유지하는 것을 말합니다. 예를 들면, 고객 정보가 담겨있는 데이터베이스에 새로운 고객이 등록되면 그 데이터베이스를 참조하는 다른 하위 계층의 데이터베이스도 같은 고객의 세부정보를 가져와야 합니다.
  • Isolation(독립성)
    • 트랜잭션이 수행되고 있을 때, 다른 트랜잭션의 연산작업이 중간에 끼어들어 기존 작업에 영향을 주지 못하도록 하는 것을 말합니다. 독립성이 보장된다면 계좌 이체작업을 진행하고 있는 도중에 계좌의 잔액을 조회한다거나 하는 작업을 동시에 수행할 수 없게 되는 것입니다.
  • Durability(지속성)
    • 트랜잭션을 성공한 후 데이터베이스에 반영된 것은 영원히 반영되어야 한다는 것을 의미합니다.

@Transactional(선언적 트랜잭션)

  • Transaction Template와 달리 트랜잭션 처리를 코드에 직접적으로 수행하지 않고, 설정 파일이나 어노테이션을 이용하여 트랜잭션의 범위, 롤백 규칙 등을 정의합니다.
  • 다음과 같은 2가지 방식으로 정의합니다.
    • <tx:advice> 태그를 이용
    • @Transactional 어노테이션을 이용한 트랜잭션 설정

현재 제가 하는 프로젝트는 @Transactional을 많이 사용하므로 해당 사항으로 정리하도록 하겠습니다.

@Transactional 옵션

propagation : 전파옵션

  • 트랜잭션 동작 도중 다른 트랜잭션을 호출(실행)하는 상황에 선택할 수 있는 옵션입니다.
    • REQUIRED
      • 디폴트 속성, 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성합니다.
    • SUPPORTS
      • 이미 시작된 트랜잭션이 있으면 참여하고, 그렇지 않으면 트랜잭션 없이 진행하게 만듭니다.
    • REQUIRES_NEW
      • 부모 트랜잭션을 무시하고 새로운 트랜잭션을 생성합니다.
      • 항상 새로운 트랜잭션을 생성합니다.
      • 이미 진행중인 트랜잭션이 있으면 트랜잭션을 잠시 보류시킵니다.
    • MANDATORY
      • REQUIRED와 비슷하게 이미 시작된 트랜잭션이 있으면 참여합니다.
      • 트랜잭션이 시작된 것이 없으면 새로 시작하는 대신 예외를 발생시킵니다.
      • 혼자서는 독립적으로 트랜잭션을 진행하면 안 되는 경우에 사용합니다.
    • NOT_SUPPORTED
      • 트랜잭션을 사용하지 않게 합니다.
      • 이미 진행 중인 트랜잭션이 있으면 보류시킵니다.
    • NESTED
      • 이미 진행중인 트랜잭션이 있으면 중첩 트랜잭션을 시작합니다.
      • 중첩 트랜잭션은 트랜잭션 안에서 다시 트랜잭션을 만드는 것입니다.

isolation : 격리수준

트랜잭션 격리 수준(isolation)이란?

동시에 여러 사용자가 데이터에 접근할 때 어디까지 허용할지 정하는 옵션입니다.

트랜잭션의 격리 수준과 데이터의 일관성은 비례합니다. 격리수준이 ↓ 데이터 접근 및 수정이 자유롭지만 일관성은 ↓, 격리수준이 ↑ 데이터 접근 및 수정이 어렵고 일관성은 ↑ 입니다.

  • DEFAULT
    • 별도로 정의하지 않으면 DB의 isolation Level을 따릅니다.
  • READ_UNCOMMITTED(Level 0)
    • 트랜잭션이 처리 중인 커밋되지 않은 데이터를 다른 트랜잭션이 접근할 수 있어, 정합성 때문에 권장되지 않습니다.
    • Dirty Read : DB에 커밋하지 않은(존재하지 않는) 데이터를 읽는 현상
      • ex )
      1. userA의 트랜잭션이 권한데이터를 조회하여 최고관리자로 변경하고 아직 커밋하지 않았습니다.
      2. userB의 트랜잭션이 위와 동일한 권한데이터를 조회해서 최고권한 데이터를 받습니다. 여기서 Dirty Read가 발생합니다.
      3. userA의 트랜잭션에서 익셉션이 발생되어 데이터를 롤백합니다. (최고관리자 → 일반사용자로) 롤백
      4. 현재 상황을 보면 실제 데이터는 일반사용자이지만, 2번에서 최고관리자 권한으로 읽고 있는 문제가 발생합니다.
  • READ_COMMITTED(Level 1)
    • 트랜잭션은 커밋한 데이터만 읽습니다.
    • Non-Repeatable read:
      • 한 트랜잭션에서 같은 쿼리를 두 번 수행할 때 그 사이에 다른 트랜잭션이 값을 수정 또는 삭제하면 두 쿼리의 결과가 상이하게 나타나는 일관성이 깨진 현상입니다.
    • oracle DB, SQL server에서 기본적으로 사용하는 Isolation level입니다.
    • Non-Repeatable read 발생
      • ex)
      1. A 트랜잭션에서 userA의 권한을 조회 : 기본권한
      2. B트랜잭션에서 userA의 권한을 관리자권한으로 변경하고 커밋
      3. A 트랜잭션에서 userA의 권한을 조회 : 관리자권한
  • REPEATABLE_READ(Level2)
    • 트랜잭션이 완료될 때까지 SELECT 문장이 사용되는 모든 데이터에 Shared Lock이 걸리는 계층입니다.

    • 트랜잭션이 범위 내에서 조회한 데이터 내용이 항상 동일함을 보장합니다.

    • 다른 사용자는 트랜잭션 영역에 해당하는 데이터에 대한 수정이 불가능합니다.

    • MySQL DBMS에서 기본으로 사용합니다.

    • Non-Repeatable read가 발생하지 않습니다.

      [DB]Undo 영역 설명 부연설명

      • ex)
      1. A트랜잭션이 userA의 권한을 조회
      2. B트랜잭션이 userA의 권한을 변경하고 커밋
      3. A트랜잭션이 userA의 권한을 다시 조회 : undo 영역에 백업된 데이터를 반환합니다.
    • Phantom read 발생

      • 한 트랜잭션 안에서 일정 범위의 레코드를 두 번이상 읽었을때, 첫번째 쿼리에서 없던 레코드가 두번째 쿼리에서 나타나는 현상입니다.
      • 트랜잭션 도중 새로운 레코드의 삽입을 허용하기 때문에 나타납니다.
  • Serializable(Level3)
    • 트랜잭션이 완료될 때까지 SELECT 문장이 사용되는 모든 데이터에 Shared Lock이 걸리는 계층입니다.
    • 가장 엄격한 격리 수준으로 완벽한 읽기 일관성 모드를 제공합니다.
    • 다른 사용자는 트랜잭션 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능합니다.

rollbackFor

기존에 트랜잭션을 작성할 때 @Transactional만을 사용했습니다. 실무에서 이와같이 작성하였는데, 피드백을 받았습니다. '익셉션이 터질 때 과연 롤백이 제대로 실행되는가?' 였습니다. 롤백을 시켜줄 거라면 rollbackFor를 작성해 달라고 하였습니다. rollbackFor가 어떤 역할을 하는지 분석하게 되었습니다.

rollbackFor는 해당하는 익셉션이 발생했을 때 롤백을 시켜줄지 결정해주는 어노테이션입니다.

기본적으로 스프링 프레임워크에서 @Transactional 어노테이션은 Checked Exception에 대해서는 롤백을 시키지 않도록 설계되어 있습니다. 그 이유는 스프링 프레임워크가 EJB의 습관을 따르기 때문입니다.

때문에, checkException의 종류인 IOException, Exception등 CheckedException에 대해서는 트랜잭션이 작동하지 않습니다.

이를 해결해 주기 위해(트랜잭션을 잘 작동시키기 위해서)는 rollbackFor에 exception을 달아줍니다.

noRollbackFor

보통 Service에서, @Transactional을 사용해서 여러 메서드를 하나의 트랜잭션으로 작동시킵니다.

이렇게 사용하게 될 시 @Transactional이 다른 @Transactional을 호출하게 되면 호출된 트랜잭션은 호출한 트랜잭션에 병합이 되고, 호출한 트랜잭션에서 에러가 발생할 경우 호출당한 트랜잭션도 롤백 처리가 됩니다. 이때, 특정 작업에 대해서 롤백을 시켜주지 않기 위해 noRollbackFor를 사용합니다.

timeout

지정한 시간 내에 해당 메소드 수행이 완료되지 않은 경우 JpaSystemException을 발생시킵니다.

  • 기본값은 : -1
  • 사용방법 : @Transactional(timeout = 1)

JpaSystemExceptionRuntimeException을 상속받기 때문에 롤백 처리됩니다.

초단위로 지정할 수 있으며 -1인 경우 timeout을 지원하지 않습니다.

readOnly

기본값은 false이며 true로 세팅하는 경우 트랜잭션을 읽기 전용으로 설정하여 insert, update, delete가 되지 않습니다.

참고 :

https://dar0m.tistory.com/225

https://starkying.tistory.com/entry/Transactional의-noRollbackFor-속성

https://bcp0109.tistory.com/322

댓글

GitHub 계정으로 로그인하시면 댓글과 질문을 남기실 수 있습니다. 남겨 주신 글은 이 저장소의 Discussions에 쌓입니다.

● main 214 posts UTF-8 LF © 2026 코징 RSS · GitHub