본문 바로가기
Development/Spring

Spring의 Transaction

by 메정 2021. 9. 23.

Spring의 Transaction

Spring은 코드 기반의 트랜잭션처리(Programmatic Transaction) 뿐만 아니라 선언적 트랜잭션(Declarative Transaction)을 지원

Spring이 제공하는 트랜잭션 템플릿 클래스를 이용하거나 설정파일, 어노테이션을 이용하여 트랜잭션의 범위 및 규칙을 정의할 수 있음

Spring에서는 주로 선언적 트랜잭션을 이용하는데, tx:advice태그 또는 @Transactional 어노테이션을 이용

Transaction(트랜잭션) 핵심 기술

  1. 트랜잭션 동기화 : 트랜잭션을 시작하기 위한 Connection 객체를 틀별한 저장소에 보관해두고, 필요할 때 꺼내쓸 수 있도록 하는 기술

    • 트랜잭션 동기화 저장소는 작업쓰레드마다 Connection 객체를 독립적으로 관리
    • 멀티쓰레드 환경에서도 충돌이 발생할 여지가 없음
    • JDBC를 이용시 사용
  2. 트랜잭션 추상화 : 애플리케이션에 각 기술마다(JDBC, JPA, Hibernate 등) 종속적인 코드를 이용하지 않고, 일관되게 트랜잭션을 처리

    Untitled

    Spring이 제공하는 트랜잭션 경계 설정을 위한 추상 인터페이스는 PlatformTransactionManager.

    이를 이용하여 트랜잭션을 공유하고, 커밋하고, 롤백 진행.

    ex.

     public Object invoke(MethodInvoation invoation) throws Throwable { 
         TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition()); 
    
         try { 
             Object ret = invoation.proceed(); 
             this.transactionManager.commit(status); 
             return ret; 
         } catch (Exception e) { 
                 this.transactionManager.rollback(status); 
                 throw e 
         } 
     }

    이 방식으로 진행할 경우 트랜잭션 관리 코드가 비즈니스 로직과 결합되어 불편함

  3. AOP를 이용한 트랜잭션 분리

    2.의 문제점을 보완한 방법

    Spring에서는 마치 트랜잭션 코드와 같은 부가 기능 코드가 존재하지 않는 것처럼 보이기 위해 해당 로직을 클래스 밖으로 빼내서 별도의 모듈로 만드는 AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)를 고안 및 적용하게 되었고, 이를 적용한 트랜잭션 어노테이션(@Transactional)을 지원

Spring의 @Transactional

Spring에서 ServiceLayer에 @Transactional어노테이션을 추가하여 Transaction 처리 진행

ex. 값을 변경, 추가, 삭제하는 메소드에 @Transactional 추가하여 설정

public interface StoreService { 
    List<StoreVO> selectStoreInfoList(StoreVO storeVO); 

    StoreVO selectStoreInfo(StoreVO storeVO); 

    @Transactional 
    int insertStoreInfo(StoreVO storeVO); 

    @Transactional 
    int updateStoreInfo(StoreVO storeVO); 

    @Transactional int deleteStoreInfo(StoreVO storeVO); 
}

클래스나 메서드 위에 @Transactional 가 추가되면 이 클래스에 트랜잭션 기능이 적용된 proxy 객체가 생성됨.

이 proxy 객체는 @Transactional 이 포함된 메소드가 호출될 경우, PlatformTransactionManager를 사용하여 트랜잭션을 시작

정상 여부에 따라 commit, rollback 수행

Spring 트랜잭션의 세부 설정

트랜잭션 전파속성(propagation)

트랜잭션의 경계에서 이미 진행중인 트랜잭션이 있거나 없을 때 어떻게 동작할지 결정하는 방식

ex. A작업에 대한 트랜잭션이 진행 중이고, B작업이 시작될 때 B작업에 대한 트랜잭션을 어떻게 할지

1. A의 트랜잭션에 참여(PROPAGATION_REQUIRED)
B의 코드는 새로운 트랜잭션을 만들지 않고 A에서 진행중이 트랜잭션에 참여.
이 경우 B의 작업이 마무리 되고 나서, 남은 A의 작업(2)을 처리할 때 예외가 발생하면 A와 B의 작업이 모두 취소
→ 왜냐하면 A와 B의 트랜잭션이 하나로 묶여있기 때문

2. 독립적인 트랜잭션 생성(PROPAGATION_REQUIRES_NEW)
B의 트랜잭션은 A의 트랜잭션과 무관하게 만든다.
이 경우 B의 트랜잭션 경계를 빠져나오는 순간 B의 트랜잭션은 독자적으로 커밋 또는 롤백, A에 어떠한 영향도 주지 않음.
→ A가 (2)번 작업을 하면서 예외가 발생해 롤백되어도 B의 작업에는 영향을 주지 못함

3. 트랜잭션 없이 동작(PROPAGATION_NOT_SUPPORTED)
B의 작업에 대해 트랜잭션을 걸지 않는다.
B의 작업이 단순 데이터 조회라면 굳이 트랜잭션이 필요 없을 것

이 외로도 다양한 처리 방법 지원 (위 3가지는 대표적인 처리 방법)

REQUIRED

미리 시작된 트랜잭션이 있으면 참여, 없으면 새로 시작

SUPPORTS

이미 시작된 트랜잭션이 있으면 참여, 그렇지 않으면 없이 진행

MANDATORY

이미 시작된 트랜잭션이 있으면 참여, 그렇지 않으면 예외 발생

REQUIRES_NEW

항상 새 트랜잭션을 시작해야 하는 경우 사용

이미 시작된 트랜잭션이 있으면, 트랜잭션 잠시 보류

NOT_SUPPORTED

이미 진행중인 트랜잭션이 있으면 보류시키고, 트랜잭션 사용하지 않도록 설정

NEVER

이미 진행중인 트랜잭션이 있으면 예외 발생, 사용하지 않도록 강제

NESTED

이미 진행중인 트랜잭션이 있으면 중첩 트랜잭션 시작

트랜잭션 내부에 다시 트랜잭션을 만듬

먼저 시작된 부모 트랜잭션의 커밋과 롤백에는 영향을 받지만, 자신의 커밋과 롤백은 부모에게 영향을 주지 않음

@Transcational 어노테이션에 propagation 엘리먼트로 전파 속성을 지정할 수 있으며, default가 REQUIRED

격리 수준(Isolation)

모든 DB 트랜잭션은 격리수준을 가지고 있어야 함.

서버에서는 여러 개의 트랜잭션이 동시에 진행될 수 있는데, 모든 트랜잭션을 독립적으로 만들고 순차 진행 한다면 안전하겠지만 성능이 크게 떨어질 수 밖에 없음.

적절하게 격리수준을 조정해서 가능한 많은 트랜잭션을 동시에 진행시키면서 문제가 발생하지 않도록 제어가 필요.

기본적으로는 DB나 DataSource에 설정된 기본 격리 수준을 따르는 것이 좋지만, 특별한 작업을 수행하는 메소드라면 독자적으로 지정해줄 필요가 있음.

DEFAULT

사용하는 데이터 액세스 기술 또는 DB 드라이버의 디폴트 설정을 따름.
일반적으로 드라이버의 격리 수준은 DB의 격리 수준을 따르며, 대부분의 DB는 READ_COMMITED를 기본 격리수준으로 갖음

READ_UNCOMMITTED

가장 낮은 격리수준
하나의 트랜잭션이 커밋되기 전에 그 변화가 다른 트랜잭션에 그대로 노출
되는 문제가 존재. 하지만 가장 빠르기 때문에 데이터의 일관성이 조금 떨어지더라도 성능을 극대화할 때 의도적으로 사용

READ_COMMITTED(DEFAULT)

가장 많이 사용되는 격리수준이다.

Spring은 기본적으로 DEFAULT로 지정되어 있고, DB는 일반적으로 READ_COMMITED로 되어있기 때문

READ_COMMITTED는 READ_UNCOMMITTED와 달리 다른 트랜잭션이 커밋하지 않은 정보는 읽을 수 없음. 대신 하나의 트랜잭션이 읽은 로우를 다른 트랜잭션이 수정할 수 있음.**

REPEATABLE_READ

하나의 트랜잭션이 읽은 로우를 다른 트랜잭션이 수정할 수 없도록 막아줌. 하지만 새로운 로우를 추가하는 것은 막지 않는다.

SELECT로 조건에 맞는 로우를 전부 가져오는 경우 트랜잭션이 끝나기 전에 추가된 로우가 발견될 수 있다.

SERIALIZABLE

가장 강력한 트랜잭션 격리 수준

이름 그대로 트랜잭션을 순차적으로 진행시켜줌.

여러 트랜잭션이 동시에 같은 테이블의 정보를 액세스할 수 없음

가장 안전하지만 가장 성능이 떨어지는 격리수준이기 때문에 극단적으로 안전한 작업이 필요한 경우가 아니라면 사용해서는 안됨!

@Transcational 어노테이션에 isolation 엘리먼트로 전파 속성을 지정할 수 있으며, 기본은 DEFAULT

제한시간

트랜잭션을 수행하는 제한시간을 설정할 수 있음

제한시간의 설정은 트랜잭션을 직접 시작하는 PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEW의 경우에 사용해야만 의미가 있음

읽기전용(readOnly)

  • 읽기 전용으로 설정하여 성능을 최적화
  • 쓰기 작업이 일어나는 것을 의도적으로 방지

일반적으로 읽기전용 트랜잭션이 시작된 이후 INSERT, UPDATE, DELETE의 작업이 진행되면 예외가 발생

롤백/커밋 예외

선언적 트랜잭션에서는 default가 런타임 예외가 발생하면 롤백

예외가 발생하지 않았거나 체크 예외가 발생하였다면 커밋

롤백/커밋의 동작 방식의 변경을 원한다면 설정을 통해 동작 방식을 바꿀 수 있음

제한시간(timeout)

timeout 속성을 이용하면 트랜잭션에 제한시간을 지정

값은 int 타입의 초 단위로 지정할 수 있는데 문자열로 지정하기를 원한다면 timeoutString을 사용

만약 별도로 값을 설정해주지 않는다면 트랜잭션 시스템의 제한시간을 따르며, 제한 시간을 직접 지정했는데 트랜잭션 매니저에서 이 기능을 지원하지 못한다면 예외가 발생

보통은 readOnly 속성 정도만 활용하고, 나머지는 디폴트 값을 사용하는 경우가 일반적이라고 함.
세밀한 속성은 DB나 WAS의 트랜잭션 매니저 설정을 이용해도 되기 때문!

참고자료

[Spring] 트랜잭션에 대한 이해와 Spring이 제공하는 Transaction(트랜잭션) 핵심 기술 - (1/3)

[Spring] 트랜잭션에 대한 이해와 Spring이 제공하는 Transaction(트랜잭션) 핵심 기술 - (2/3)

[Spring] 트랜잭션에 대한 이해와 Spring이 제공하는 Transaction(트랜잭션) 핵심 기술 - (3/3)

트랜잭션의격리수준

댓글