Spring Transaction

Panda·2023년 6월 26일
0

Spring

목록 보기
39/42

스프링에서 Transaction을 어떻게 관리하고 작동하는 지 알아보려고 합니다

트랜잭션의 종류가 꼭 DB에만 속한건 아니지만 여기서 말하는 트랜잭션은 DB 트랜잭션을 말할 예정입니다.

Transaction

만약 데이터베이스의 데이터를 추가 또는 변경하는 도중에 예외가 발생한다면 DB의 데이터들은 작업이 일어나기 전의 상태로 돌아가게 되고(Rollback)
정상적인 데이터 처리를 진행하였으면 작업 결과를 적용하는(Commit) 이것이 트랜잭션의 개념 입니다.

ACID

  • Atomicity (원자성): 트랜잭션 내의 작업들은 모두 성공 또는 모두 실패한다.
  • Consistency (일관성): 모든 트랜잭션은 일관성 있는 DB 상태를 유지한다. (ex: DB의 무결성 제약 조건 항상 만족)
  • Isolation (격리성) : 동시에 실행되는 트랜잭션들은 서로 영향을 미치지 않는다. (ex: 동시에 같은 데이터 수정 X)
  • Durability (지속성) : 트랜잭션이 성공적으로 끝나면 그 결과는 항상 기록되어야 한다.

위의 특징들은 트랜잭션의 가장 기본적인 개념이자 진짜 중요한 개념입니다.
진~~~짜 중요해요 두번 강조합니다.

Spring Transaction

스프링에서는 크게 2가지 방식으로 트랜잭션을 관리할 수 있는 방법을 제공합니다.

1. TransactionTemplate

스프링에서 데이터 관리 방법들은 JdbcTemplate, JPA 등 여러가지가 존재하는데 해당 기술마다 트랜잭션을 사용하는 방법도 달라집니다.
Spring에서는 PlatformTransactionManager 인터페이스를 이용한 트랜잭션 관리를 제공합니다.
해당 인터페이스는 총 3가지 기능을 가지고 있는데 밑에와 같습니다.

  • getTransaction : 현재 TransactionStatus를 return
  • commit : 변경 내역 커밋
  • rollback : 변경 내역 롤백

따라서 PlatformTransactionManager를 명시적으로 사용해 저희가 원하는 트랜잭션을 구성하여 관리할 수 있습니다. (직접 AOP 만들어 트랜잭션 관리를 해준다던지 등등)

TransactionStatus

트랜잭션 실행를 제어하고 상태를 조회하는 방법을 제공

최종적으로 제공하는 메소드는 밑에와 같습니다.

// TransactionStatus 기능
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
void flush();
boolean isCompleted();

참고로 JpaTransactionManager으로 JPA, Mybatis 둘다 하나의 트랜잭션에서 같이 관리할 수 있습니다!!
그 이유는 JpaTransactionManager가 DataSource 접근이 가능하여 일반적인 JDBC를 사용 가능하기 때문입니다.
이때 Mybatis SqlSession에 트랜잭션을 위임하는게 아닌 Spring Transaction에 위임합니다.

2. @Transactional

@Transactional 어노테이션을 사용하게되면 AOP를 통한 트랜잭션 관리가 위와 같이 진행하게 됩니다.

AOP를 통한 트랜잭션 관리가 되어 저희는 어노테이션만 붙이면 되는 편한 방식입니다.
아마 대부분의 사람들이 해당 방식으로 트랜잭션을 관리하지 않을까 싶네요.
실질적인 트랜잭션 관리 로직은 PlatformTransactionManager 사용하는 것처럼 똑같이 작동하게 됩니다.

단 테스트 환경일 시에는 @Transactional 이 성공, 실패와 상관없이 롤백한다는 점 기억하면 될 것같습니다. (아무런 옵션 없이 사용했을 때)

전파 유형

여러 트랜잭션이 동시에 처리될 때 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터들을 볼 수 있게 허용할지 말지를 결정하는 것 (DB Lock 이랑 살짝 비슷한 느낌이네요)

  • REQUIRED

    • @Transactional 기본값
    • 이미 시작된 트랜잭션이 있으면 참여하고 없으면 새로 시작
    • 하나의 트랜잭션이 시작된 후 다른 트랜잭션 경계가 설정된 메소드를 호출하면 같은 트랜잭션으로 묶임
  • REQUIRES_NEW

    • 항상 새로운 트랜잭션을 시작해야 하는 경우에 사용
    • 이미 진행중인 트랜잭션이 있으면 이를 보류시키고 새로운 트랜잭션을 만들어 시작함
  • NESTED

    • 이미 진행중인 트랜잭션이 있으면 중첩(자식) 트랜잭션을 시작함
    • 중첩 트랜잭션은 트랜잭션 안에 다시 트랜잭션을 만드는 것으로, 독립적인 트랜잭션을 만드는 REQUIRES_NEW와는 다름
    • NESTED에 의한 중첩 트랜잭션은 먼저 시작된 부모 트랜잭션의 커밋과 롤백에는 영향을 받지만, 자신의 커밋과 롤백은 부모 트랜잭션에게 영향을 주지 않음
  • SUPPORTS

    • 이미 시작된 트랜잭션이 있으면 참여하고, 그렇지 않으면 트랜잭션 없이 진행함
    • 트랜잭션이 없어도 해당 경계 안에서 Connection 객체나 하이버네이트의 Session 등은 공유 할 수 있음
  • Mandatory

    • 이미 시작된 트랜잭션이 있으면 참여하고, 없으면 새로 시작하는 대신 없으면 예외를 발생시킴
    • MANDATORY는 혼자서 독립적으로 트랜잭션을 진행하면 안되는 경우에 사용
  • Not Supported

    • 이미 진행중인 트랜잭션이 있으면 이를 보류시키고, 트랜잭션을 사용하지 않도록 함
  • Never

    • 이미 진행중인 트랜잭션이 있으면 예외를 발생시키며, 트랜잭션을 사용하지 않도록 강제함

격리 수준

동시 트랜잭션에 의해 적용된 변경 사항이 서로에게 표시되는 방식을 설명합니다.
동시 트랜잭션은 여러 사용자가 동시에 실행할 수 있는 트랜잭션입니다.
격리 수준은 트랜잭션에서 0개 이상의 동시성 부작용을 방지합니다.

DB에 따른 격리 수준을 지원하고 안할 수 있으니 사용하는 DB가 어떠한 격리 수준을 지원하는지 확인 바랍니다.

  • Default

    • DB의 격리 수준을 따릅니다
  • Read Uncommitted (Isolation.READ_UNCOMMITTED)

    • READ_UNCOMMITTED는 가장 낮은 격리 수준이며 가장 많은 동시 액세스를 허용합니다.
    • 이 격리 수준을 가진 트랜잭션은 다른 동시성 트랜잭션의 커밋되지 않은 데이터를 읽습니다. (이거 진짜 중요한 동시성 부작용이니 항상 생각하면서 개발해야 합니다.)
  • Read Committed (Isolation.READ_COMMITTED)

    • Read Committed은 커밋되지 않은 변경 내용을 읽는 것을 방지합니다.
    • 대부분의 DB 격리 수준 기본값입니다.
  • Repeatable Read (Isolation.REPEATABLE_READ)

    • Repeatable Read은 동시 트랜잭션에서 커밋되지 않은 변경 사항의 영향을 받지 않고 행에 대해 다시 쿼리해도 다른 결과를 얻지 않습니다.
    • 두 개 이상의 동시 트랜잭션이 동일한 행을 읽고 업데이트할 때 발생하는 업데이트 손실 방지를 위한 최소 요구 수준입니다.
    • 즉 Repeatable Read는 한 행에 대한 동시 액세스를 전혀 허용하지 않습니다.
    • MySQL 기본 격리수준입니다. (오라클은 지원안함, 대신에 SELECT FOR UPDATE 쿼리 쓰면 될 듯)
    • 제가 생각했을 때 가장 이상적인 격리 수준이 아닌가 싶습니다. (하나의 레코드에 대해서 Lock 거는거랑 거의 유사하니)
  • Serializable (Isolation.SERIALIZABLE)
    • Serializable 가장 높은 격리 수준입니다.
    • 모든 동시성 부작용을 방지하지만, 동시 트랜잭션 호출을 순차적으로 실행하기 때문에 트랜잭션 처리 시간이 가장 낮을 수 있습니다.

느낀 점

학생 때 개발 했을 때 기본적인 어노테이션만 사용해서 에러시 롤백 사용하는 정도였는데
실무에서 개발을 해보니까 트랜잭션이 너무나도 중요하다는걸 느끼는 중이여서 재밌네요

@Transactional 방식이 아닌 PlatformTransactionManager로 직접 트랜잭션 관리를 하는 도중이라서 이러한 이해가 필요하였습니다.

특히나 동시성 문제로 인해 격리 수준 설정은 가장 많이 마주치고 복잡한 문제 중 하나인데 어떻게 해결해야 할지 생각을 많이 하게 되네요
트랜잭션 격리 수준을 설정을 해서 해결해도 되고 아니면 DB Lock을 걸어 해결해도 되고~~

트랜잭션에 대해서 좀더 깊은 이해를 한 것 같아서 재밌습니다~~!

profile
실력있는 개발자가 되보자!

0개의 댓글