ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Transactional
    STUDY/DB 2023. 5. 10. 23:08

     

    트랜잭션이란?

    데이터베이스의 상태를 변경시키는 작업 또는 한번에 수행되어야하는 연산들을 말한다. 트랜잭션 작업이 끝나면 commit 또는 rollback 되어야한다.

     

    트랜잭션 성질

    • 원자성(Atomicity) : 한 트랜잭션 내에서 실행한 작업들은 하나의 단위로 처리한다. 즉 모두 성공 또는 모두 실패이다.
    • 일관성(Consistency) : 트랜잭션은 일관성 있는 데이터베이스 상태를 유지한다.
    • 격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로 영향을 미치지 않도록 격리해야한다.
    • 지속성(Durability) : 트랜잭션이 성공적으로 처리되면 결과가 항상 저장되어야 한다.

    Spring에서의 트랜잭션 활용

    • 비즈니스 로직과 결합
    • 읽기 전용 트랜잭션의 공통화
    • 테스트의 DB 커밋을 롤백

     

    Spring이 제공하는 트랜잭션 핵심 기술

    1. 트랜잭션(Transaction) 동기화
    2. 트랜잭션(Transaction) 추상화
    3. AOP를 이용한 트랜잭션(Transaction) 분리

     

    트랜잭션 동기화는 트랜잭션을 시작하기 위한 Connection 객체를 특별한 저장소에 보관해두고 필요할 때 꺼내쓸 수 있도록 하는 기술이다. 트랜잭션 동기화 저장소는 작업 쓰레드 마다 Connection 객체를 독립적으로 관리하기 때문에, 멀티쓰레드 환경에서도 충돌이 발생하지 않는다. 하지만 특정 기술(ex, JDBC)에 종속적인 코드를 사용하기 때문에, 다른 기술(ex. Hibernate)를 사용할 경우 문제가 발생할 수 있다. 이를 해결하기 위해 Spring은 트랜잭션 관리 부분을 추상화한 기술을 제공한다.

     

    트랜잭션 추상화 기술을 통해 애플리케이션에 각 기술마다 종속적인 코드를 사용하지 않고 일관되게 트랜잭션을 처리할 수 있도록 한다. Spring은 트랜잭션 경계 설정을 위한 추상 인터페이스인 PlatformTransactionManager를 제공한다. 이를 통해 사용하는 기술과 상관없이 트랜잭션을 공유, 커밋, 롤백할 수 있다. 하지만 이런 트랜잭션 관리 코드는 비즈니스 로직과 결합되어 있는데, Spring은 AOP를 이용해 트랜잭션 부분을 비즈니스 로직과 분리한다.

     

    트랜잭션 코드와 비즈니스 로직 코드가 복잡하게 얽혀있는 코드는 여러 책임을 가질 뿐만 아니라 서로 성격이 다르므로 분리하는 것이 적합하다. 이를 위해 Spring에서는 트랜잭션 코드를 클래스 밖으로 빼내서 별도의 모듈로 만드는 AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)를 적용한 트랜잭션 어노테이션(@Transactional)을 지원한다.

     

    @Transactional

    Spring에서 트랜잭션 처리를 위해 지원하는 어노테이션으로, 클래스나 인터페이스 또는 메소드에 선언해서 사용되며, 이 어노테이션이 붙으면 스프링은 해당 타깃을 포인트 컷의 대상으로 자동 등록하며, 트랜잭션 관리 대상이 된다. 이렇게 AOP를 이용해 코드 외부에서 트랜잭션 기능을 부여해주고, 속성을 지정할 수 있게 해주는 것을 선언적 트랜잭션이라고 한다. @Transactional 어노테이션을 사용하면 트랜잭션 속성을 메소드 단위로 다르게 지정할 수 있어 매우 세밀한 트랜잭션 속성 제어가 가능하다.

     

    클래스나 메서드 위에 @Transactional 이 추가되면, 이 클래스에 트랜잭션 기능이 적용된 프록시 객체가 생성된다. 이 프록시 객체는 @Transactional이 포함된 메소드가 호출 될 경우, PlatformTransactionManager를 사용하여 트랜잭션을 시작하고, 정상 여부에 따라 Commit 또는 Rollback 한다.

    * 프록시 객체 : @Transactional 어노테이션이 추가된 클래스나 메서드를 대신하여 트랜잭션 관리를 수행

     

    @Transactional을 사용했을 때 롤백 처리는 어떤 예외인지에 따라 동작이 다르다. Unchecked Exception(언체크 예외)이 발생한 경우에는 자동적으로 롤백이 발생하지만, Checked Exception(체크 예외)이 발생한 경우에는 롤백이 되지 않는다. 체크 예외를 롤백시키기 위해서는 rollbackFor 속성으로 해당 체크 예외를 적용시켜야 한다.

    * 언체크 예외나 Error만 롤백 대상으로 보는 이유는 해당 예외들은 복구 가능성이 없는 예외들이므로 try-catch나 throw를 통해 처리를 강제하지 않기 때문이다. 반면 체크 예외를 커밋 대상으로 삼는 이유는 체크 예외가 예외적인 상황에서 사용되기 보다는 리턴 값을 대신해서 비즈니스 적인 의미를 담은 결과로 돌려주는 용도로 사용되기 때문이다.

     

    @Transactional 어노테이션은 메소드 외에도 클래스나 인터페이스에도 붙일 수 있는데, 트랜잭션의 적용 우선 순위는 클래스의 메소드 > 클래스 > 인터페이스의 메소드 > 인터페이스 순이다. 해당 순으로 어노테이션이 적용됐는지 차례로 확인하고, 가장 먼저 발견되는 속성 정보를 사용해 트랜잭션을 관리한다. 이를 4단계의 대체 정책(fallback policy)이라고 한다.

     

    @Transactional(readOnly=...,
                   isolation=...,
                   propagation=...,
                   timeout=...,
                   rollbackFor=..., rollbackForClassName=...,
                   noRollbackFor=..., noRollbackForClassName=...)

     

    Spring 트랜잭션 설정

    • 트랜잭션 전파(Propagation)
    • 격리수준(Isolation)
    • 롤백/커밋(rollback/commit) 예외
    • 제한시간(timeout)
    • 읽기전용(readOnly)

     

    Propagation(전파 속성)

     

    @Transactional(propagation=Propagation.REQUIRED)

     

    @Transactional의 장점 중 하나는 여러 트랜잭션 적용 범위를 묶어서 커다란 하나의 트랜잭션 경계를 만들 수 있다는 점이다. 이미 트랜잭션이 진행중일 때 새로운 트랜잭션 진행을 어떻게 할지 결정하는 것을 전파 속성이라 한다. 전파 속성을 통해 새로운 트랜잭션을 시작할지 또는 기존의 트랜잭션에 참여할지 등을 결정하게 된다.

     

    한 트랜잭션이 다른 트랜잭션을 만나는 상황

     

    트랜잭션은 데이터베이스에서 제공하는 기술이므로 커넥션 객체를 통해 처리한다. 그래서 하나의 트랜잭션을 사용한다는 것은 하나의 커넥션 객체를 사용한다는 것으로, 실제 데이터베이스의 트랜잭션을 사용한다는 점에서 물리 트랜잭션이라고도 한다.

     

    트랜잭션 전파 속성에 따라서 외부 트랜잭션과 내부 트랜잭션이 동일한 트랜잭션을 사용할 수도 있다. 하지만 스프링 입장에서는 트랜잭션 매니저를 통해 트랜잭션을 처리하는 곳이 두군데가 되는데, 실제 데이터베이스 트랜잭션과 스프링이 처리하는 트랜잭션 영역을 구분하기 위해 논리 트랜잭션이라는 개념을 추가했다. 

     

    물리 트랜잭션 : 실제 데이터베이스에 적용되는 트랜잭션으로, 커넥션을 통해 커밋/롤백하는 단위

    논리 트랜잭션 : 스프링이 트랜잭션 매니저를 통해 트랜잭션을 처리하는 단위

     

    여러 트랜잭션이 존재할 경우, 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋되고, 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다.

     

     

    위 그림은 기본적으로 2개의 논리 트랜잭션을 묶어 1개의 물리 트랜잭션을 사용하는 것이다. REQUIRED 속성이 해당되며, 내부 트랜잭션은 기존에 존재하는 외부 트랜잭션에 참여하게 된다. 즉, 외부 트랜잭션의 범위가 내부까지 확장되는 것으로, 내부 트랜잭션은 새로운 물리 트랜잭션을 사용하지 않는다.

     

    하지만 트랜잭션 매니저에 의해 관리되는 논리 트랜잭션이 존재하므로 커밋은 내부 1회, 외부 1회해서 총 2회 실행된다. 내부 트랜잭션은 논리 트랜잭션이기 때문에 커밋을 호출해도 즉시 커밋되지는 않고, 외부 트랜잭션이 최종적으로 커밋될 때 실제로 커밋이 된다. 또한 내부 트랜잭션에서 롤백을 하여도 즉시 롤백되지 않고, 물리 트랜잭션이 롤백될 때 실제 롤백이 처리되는데, 논리 트랜잭션들 중에서 1개라도 롤백되었다면 롤백된다.

     

     

    위 그림은 외부 트랜잭션과 내부 트랜잭션이 완전리 분리되는 것으로 REQUIRED_NEW 전파 속성이 해당되며, 2개의 물리 트랜잭션이 사용되며 각각 트랜잭션 별로 커밋과 롤백이 수행된다. 서로 다른 물리 트랜잭션을 사용하기 때문에, 내부 트랜잭션 롤백이 외부 트랜잭션에 영향을 주지 않는다. 내부 트랜잭션이 롤백할 경우, 실제 커넥션에 롤백을 호출하는 것이므로 트랜잭션이 끝나게 된다. 

     

    서로 다른 물리 트랜잭션을 별도로 가지는 것은 각각 다른 커넥션이 사용된다는 것으로, 하나의 HTTP 요청에 2개의 커넥션이 사용되는 것이다. 내부 트랜잭션이 처리 중일때는 외부 트랜잭션이 대기하게 되는데, 이는 데이터베이스 커넥션을 고갈시킬 수 있으므로 조심해야 한다.

     

    REQUIRED

    • 디폴트 속성으로 모든 트랜잭션 매니저가 지원한다.
    • 이미 진행중인 트랜잭션이 존재할 경우 해당 트랜잭션에 참여하고, 없으면 새로 시작한다.
    • 하나의 트랜잭션이 시작된 후 다른 트랜잭션 경계가 설정된 메서드를 호출하면 같은 트랜잭션으로 묶인다.
    • 가장 간단하고 자연스러운 트랜잭션 전파 방식이지만, 매우 강력하고 유용하다.

     

    SUPPORTS

    • 이미 진행중인 트랜잭션이 있다면 해당 트랜잭션에 참여하고, 만약 진행중인 트랜잭션이 없다면 트랜잭션 없이 진행한다.
    • 트랜잭션이 없어도 해당 경계 안에서 Connection 객체나 하이버네이트의 Session 등은 공유할 수 있다.

     

    MANDATORY

    • 이미 진행중인 트랜잭션이 있으면 참여하고, 없으면 새로 시작하는 대신 예외를 발생시킨다.
    • 진행중인 트랜잭션이 존재해야 하기 때문에, 혼자서 독립적으로 트랜잭션을 진행하면 안되는 경우 사용할 수 있다.

     

    REQUIRED_NEW

    • 항상 새로운 트랜잭션을 생성한다.
    • 만약 이미 진행중인 트랜잭션이 존재한다면 잠깐 보류하고, 새로운 트랜잭션을 만들어 먼저 진행한다.

     

    NOT_SUPPORTED

    • 이미 진행중인 트랜잭션이 있다면 이를 보류시키고, 트랜잭션 없이 작업을 수행한다.

     

    NEVER

    • 진행중인 트랜잭션이 없을 때 작업을 수행하며, 만약 진행중인 트랜잭션이 존재한다면 예외를 발생시킨다.
    • 트랜잭션을 사용하지 않도록 강제한다.

     

    NESTED

    • 이미 진행중인 트랜잭션이 있으면 중첩된(자식) 트랜잭션을 시작하고, 존재하지 않으면 REQUIRED와 동일하게 실행된다.
    • 중첩 트랜잭션은 트랜잭션 안에 다시 트랜잭션을 만드는 것으로, 독립적인 트랜잭션을 만드는 REQUIRED_NEW와는 다르다.
    • NESTED에 의한 중첩 트랜잭션은 먼저 시작된 부모 트랜잭션의 커밋과 롤백에는 영향을 받지만, 자식의 커밋과 롤백은 부모 트랜잭션에게 영향을 주지 않는다.

     

     

    Isolation(격리수준)

     

    @Transactional(isolation=Isolation.DEFAULT)

     

    트랜잭션 격리수준은 동시에 여러 트랜잭션이 진행될 때 트랜잭션의 작업 결과를 다른 트랜잭션에게 어떻게 노출할 것인지를 결정한다.

    • DEFAULT : 사용하는 DB 드라이버의 디폴트 설정을 따른다. 대부분 READ_COMMITED를 기본 격리수준으로 설정한다.
    • READ_UNCOMMITED : 가장 낮은 격리 수준이다. 트랜잭션이 커밋되기 전에 그 변화가 다른 트랜잭션에 그대로 노출된다. 예를들어, 트랜잭션A가 어떤 값을 x에서 y로 변경하고 아직 커밋하지 않은 상황에서 트랜잭션B가 해당 값을 읽을 경우 트랜잭션B에 y가 조회되는 것이다. 하지만 속도가 빠르기 때문에 데이터의 일관성이 떨어지더라도, 성능 극대화를 위해 의도적으로 사용하기도 한다. 
    • READ_COMMITED : 트랜잭션이 커밋하지 않은 정보는 읽을 수 없다. 하지만 트랜잭션이 읽은 로우를 다른 트랜잭션에서 수정할 수 있다. 그래서 트랜잭션이 같은 로우를 읽었어도 시간에 따라서 다른 내용이 발견될 수 있다. 예를들어 트랜잭션A가 수정중인 어떤 값에 대해서 트랜잭션B는 읽을 수 없다. 반대로 트랜잭션B가 어떤 값을 두 번 조회하는 과정 중 첫번째 조회가 끝나고 두번째 조회가 이루어지는 사이에 트랜잭션A가 해당값을 수정하면 트랜잭션B는 두번째 조회에서 수정된 값을 조회하게 된다.
    • REPEATABLE_READ : 트랜잭션이 읽은 로우를 다른 트랜잭션에서 수정되는 것을 막아준다. 예를 들어 트랜잭션A가 어떤 값을 두번 조회할 때 그 과정에서 중간에 트랜잭션B가 해당값을 수정하려하지만, 이미 트랜잭션A가 읽은 로우이기 때문에 수정이 이루어지지 않아서 트랜잭션A는 두번 모두 일정한 값을 조회할 수 있다. 하지만 새로운 로우를 추가하는 것은 제한하지 않는다.
    • SERIALIZABLE : 가장 강력한 트랜잭션 격리수준이다. 여러 트랜잭션이 동시에 같은 테이블 로우에 엑세스하지 못하게 한다. 가장 안전하지만 가장 성능이 떨어진다. 예를들어 트랜잭션 A가 어떤 값을 조회하는 동안 모든 데이터에 대해 shared lock이 걸려 트랜잭션B는 그 영역에 해당되는 데이터에 대한 추가 및 수정이 불가능하다.

     

    Rollback/Commit 예외

     

    @Transactional(noRollbackFor=Exception.class, rollbackFor=Exception.class)

     

    롤백/커밋의 동작 방식을 변경할 때 사용하는 설정이다. 커밋 대상이지만 롤백을 발생시킬 예외나 클래스 이름은 각각 rollbackFor 또는 rollbackForClassName으로 지정할 수 있고, 반대로 롤백 대상이지만 커밋하기 위해서는 noRollbackFor 또는 noRollbackForClassName을 지정해서 사용하면 된다.

     

    timeout(제한시간)

     

    @Transactional(timeout=10)

     

    트랜잭션에 제한시간을 지정한다. 값은 int 타입의 초 단위로 지정할 수 있으며, 문자열로 지정할 경우 timeoutString을 사용하면 된다. -1로 설정할 경우에는 트랜잭션 제한시간을 사용하지 않으며, 별도로 값을 설정하지 않을 경우에는 트랜잭션 시스템의 제한시간을 따른다. 지정한 시간 내에 수행이 완료되지 않으면 롤백한다.

     

    readOnly(읽기전용)

     

    @Transactional(readOnly = true)

     

    트랜잭션을 읽기 전용으로 설정한다. 보통 성능 최적화와 쓰기 작업 방지 목적으로 사용된다. 기본값은 false 이고, 읽기전용 트랜잭션이 시작된 후 INSERT, UPDATE, DELETE 작업이 진행되면 예외가 발생한다.

     

     

     

     

     

    참고
    https://mangkyu.tistory.com/269

    'STUDY > DB' 카테고리의 다른 글

    RAC(Real Application Clusters)  (0) 2023.05.24
    DBCP(Database Connection Pool)  (0) 2023.05.17
    Database Lock  (0) 2023.04.26
    Isolation Level  (0) 2023.04.25
    실행계획  (0) 2023.04.19
Designed by Tistory.