@Transactional

초코칩·2024년 5월 16일
0

spring

목록 보기
13/16
post-thumbnail

트랜잭션

트랜잭션은 데이터베이스에서 원자성, 일관성, 격리성, 지속성(ACID) 원칙을 따르며 수행되는 작업의 단위이다. 간단히 말해, 하나 이상의 데이터베이스 조작(읽기 또는 쓰기)을 포함하는 논리적인 작업 단위를 나타낸다. 이러한 작업은 전체적으로 성공하거나 실패하며, 성공할 경우에는 영구적으로 반영되고 실패할 경우에는 모든 변경 사항이 롤백된다. 따라서 트랜잭션은 데이터베이스의 무결성을 보장하고 데이터베이스 조작 간의 일관성을 유지하는 데 중요한 역할을 한다.

public void sendMoney(Member sender, Member receiver, double amount){
	Account senderAccount = accountRepository.findAccountByMember(sender);
	Account receiverAccount = accountRepository.findAccountByMember(receiver);
        
	senderAccount.withdraw(amount);		// Dirty Checking Update
	receiverAccount.deposit(amount);	// Dirty Checking Update
}

이렇게 두 개 이상의 쿼리를 한 작업으로 실행해야 할 때 사용하는 것이 트랜잭션(transaction)이다. 트랜잭션은 여러 쿼리를 논리적으로 하나의 작업으로 묶어준다. 한 트랜잭션으로 묶인 쿼리 중 하나라도 실패하면 전체 쿼리를 실패로 간주하고 실패 이전에 실행한 쿼리를 취소한다.

트랜잭션의 ACID

쿼리 실행 결과를 취소하고 DB를 기존 상태로 되돌리는 것을 롤백(RollBack)이라고 부른다. 반면에 트랜잭션으로 묶인 모든 쿼리가 성공해서 쿼리 결과를 DB에 실제로 반영하는 것을 커밋(Commit)이라 한다.

스프링 트랜잭션 추상화 레이어

애플리케이션 개발에 스프링을 사용할지 여부와 상관없이, 트랜잭션을 사용할 때는 먼저 글로벌 트랜잭션을 사용할지 로컬 트랜잭션을 사용할지 선택해야 한다. 로컬 트랜잭션은 단일 트랜잭션 자원(예를 들어 JDBC 연결)에 한정되는 반면에, 글로벌 트랜잭션은 컨테이너가 관리하며 여러 트랜잭션 자원에 걸쳐있을 수 있다.

로컬 트랜잭션

로컬 트랜잭션은 관리하기 쉬우며 애플리케이션의 모든 처리가 하나의 트랜잭션 자원(예를 들어 JDBC 트랜잭션)만 사용해 이루어진다면 로컬 트랜잭션을 사용해도 충분하다. 하지만 스프링과 같은 애플리케이션 프레임워크를 사용하지 않는다면 많은 트랜잭션 관리 코드를 작성해야 하며, 향후에 트랜잭션이 다중 트랜잭션 리소스에 걸치도록 확장해야 한다면 로컬 트랜잭션 관리 코드를 제거하고 글로벌 트랜잭션을 사용하도록 다시 개발해야 한다.

글로벌 트랜잭션

자바 세계에서는 JTA(Java Transaction API)로 글로벌 트랜잭션을 구현한다. JTA Transaction Manager는 각 분산 자원에 설치된 리소스 매니저를 이용해 다중 트랜잭션 리소스에 접근한다. 각 리소스 매니저와 통신은 XA 프로토콜(분산 트랜잭션을 정의하는 개방형 표준)을 사용한다. 또한, 2단계 커밋(2 Phase Commit, 2PC) 메커니즘을 사용해 모든 백엔드 데이터 소스가 모두 업데이트 되거나 모두 롤백되도록 보장한다. 백엔드 리소스 중 하나라도 처리가 실패하면 전체 트랜잭션 이 롤백되므로 다른 자원에 대한 수정도 롤백된다.

아래의 주요 부분이 글로벌 트랜잭션(일반적으로 분산 트랜잭션이라고도 함)을 이루고 있다.

  • Resource Manager: 일반적으로 백엔드 리소스 공급 업체가 제공하며 백엔드 리소스에 접근하는데 사용된다. 예를 들어 MySQL DB에 연결할 때는 MySQL 자바 커넥터가 제공하는 MySQLXADataSource 클래스를 사용해 접근해야 한다.
  • JTA Transaction Manager: 부분은 JTA 트랜잭션 매니저로, 트랜잭션에 참여하는 모든 리소스 매니저의 트랜잭션 상태를 관리, 조정, 동기화를 담당한다. 이때 분산 트랜잭션 처리에 널리 사용되는 공개 표준인 XA 프로 토콜이 사용된다.
  • Application Component: 애플리케이션 자체나 애플리케이션이 실행되는 기본 컨테이너 또는 애플리케이션이 실행되는 스프링 프레임워크가 트랜잭션을 관리(시작, 커밋, 트랜색션 롤백 등)한다. 이때 애플리케이션은 JEE에서 정의한 다양한 표준으로 기본 백엔드 리소스에 접근한다. JDBC를 이용해서 RDBMS에, JMS를 이용 해서 MQ에, 자바 EE 커넥터 아키텍처 (Java EE Connector Architecture, JCA)를 이용해서 ERP에 연결한다.

PlatformTransactionManager 구현체

스프링에서 PlatformTransactionManager 인터페이스는 TransactionDefinition 인터페이스와 TransactionStatus 인터페이스를 사용해 트랜잭션을 생성하고 관리한다. 이러한 인터페이스를 실제로 구현하려면 상세한 트랜잭션 매니저 관련 지식이 있어야 한다.

스프링은 PlatformTransactionManager 인터페이스의 다양한 구현체를 제공한다. CciLocalTransactionManager 클래스는 JEE, JCA, 공통 클라이언트 인터페이스(Common Client Interface, CCI)를 지원한다. DataSourceTrainsactionManager 클래스는 일반적인 JDBC 연결을 위한 클래스이다. ORM과 관련해서는 JPA(JpaTransactionManager 클래스와 하이버네이트 5(HibernateTransactionManager 클래스)를 포함한 많은 구현체가 있다. JTA 의 범용 구현체 클래스는 JtaTransactionManager이다. 또한, 스프링은 특정 애플리케이션 서버에 특화된 여러 JTA 트랜잭션 매니저 클래스를 제공한다.

@Transactional을 이용한 트랜잭션 처리

스프링이 제공하는 @Transactional 어노테이션을 사용하면 트랜잭션 범위를 설정할 수 있다. 다음과 같이 트랜잭션 범위에서 실행하고 싶은 메서드에 @Transactional 어노테이션을 붙일 수 있다.

@Transactional
public void sendMoney(Member sender, Member receiver, double amount){
  	Account senderAccount = accountRepository.findAccountByMember(sender);
  	Account receiverAccount = accountRepository.findAccountByMember(receiver);

  	senderAccount.withdraw(amount);
	receiverAccount.deposit(amount);
}

@Transactional을 클래스 수준에 적용하면, 기본적으로 스프링은 클래스 내의 각 메서드 실행 전에 트랜잭션이 존재함을 보장한다.

주요 속성

트랜잭션은 4개의 잘 알려진 ACID 프로퍼티(원자성(atomicity), 일관성(consistency), 격리(isolation), 내구성(durability))를 갖고 있으며, 트랜잭션 리소스는 이러한 관점으로 트랜잭션을 관리해야 할 책임이 있다. 트랜잭션의 원자성, 일관성, 내구성을 제어할 수 없지만, 트랜잭션 전파(propagation)와 시간 초과(timeout)를 제어할 수 있을 뿐 아니라, 트랜잭션을 읽기 전용(read-only)으로 구성하고 격리 수준을 지정할 수도 있다.

스프링은 이런 모든 설정을 TransactionDefinition 인터페이스에 캡슐화한다. 이 TransactionDefinition 인터페이스는 트랜잭션을 지원하는 스프링의 핵심 인터페이스인 PlatformTransactionManager에서 사용되며, PlatformTransaactionManager의 여러 구현체는 JDBC나 JTA와 같은 특정 플랫폼에서 트랜잭션 관리를 수행한다.

public interface TransactionDefinition {
	// ...
    int getPropafationBehavior();
    int getIsolationLevel();
    int getTimeout();
    boolean isReadOnly();
    String getNamt();
}

핵심 메서드인 PlatformTransactionManager.getTransaction()은 Transaction]Definition 인터페이스를 인수로 전달받고 TransactionStatus 인터페이스를 반환한다. TransactionStatus 인터페이스는 트랜잭션 실행을 제어할 때 사용되는데, 특히 트랜잭션 결과를 설정하고 트랜잭션의 완료 여부나 새 트랜잭션인지의 여부를 확인하는데 사용된다.

속성타입설명
valueString트랜잭션을 관리할 때 사용할 PlatformTransactionManager 빈의 이름을 지정한다. 기본값은 " "이다.
propagationPropagation트랜잭션 전파 타입을 지정한다.
isolationIsolation트랜잭션 격리 레벨을 지정한다.
timeoutint트랜잭션 제한 시간을 초 단위로 지정한다.

속성을 사용하지 않고 @Transactional 어노테이션만 적용하면 트랜잭션 전파는 required, isolation는 default, timeout은 default(DB timeout), 모드는 read-write가 된다.

findBy~()과 같은 메서드는 @Transactional(readOnly=true)로 설정할 경우 persistence 제공자가 read-only 트랜잭션에 대해 일정한 수준으로 최적화를 수행한다. 예를 들어 Hibernate read-only 상태에서 데이터베이스에서 조회한 관리 대상 인스턴스의 스냅샷을 유지하지 않는다.

value

@Transactional 애노테이션의 value 속성값이 없으면 등록된 빈 중에서 타입이 PlatformTransactionManager인 빈을 사용한다.

// AppCtx 설정 클래스의 플랫폼 트랜잭션 매니저 빈 설정 
@Bean 
public PlatformTransaactionManager transactionManager() { 		
	DataSourceTransactionManager tm = new DataSourceTranssactionManager(): 	
	tm.setDataSource(dataSource()); 
    return tm; 
} 

Propagation

Propagation 열거 타입에 정의되어 있는 값 목록은 아래와 같다.

설명
REQUIRED메서드를 수행하는 데 트랜잭션이 필요하다는 것을 의미한다. 현재 진행중인 트랜잭션이 존재하면 해당 트랜잭션을 사용한다. 존재하지 않으면 새로운 트랜잭션을 생성한다.
MANDATORY메서드를 수행하는 데 트랜잭션이 필요하다는 것을 의미한다. 하지만 REQUIRED와 달리 진행 중인 트랜잭션이 존재하지 않을 경우 익셉션이 발생한다.
REQUIRES_NEW항상 새로운 트랜잭션을 시작한다. 진행 중인 트랜잭션이 존재하면 기존 트랜잭션을 일시 중지하고 새로운 트랜잭션을 시작한다. 새로 시작된 트 랜잭션이 종료된 뒤에 기존 트랜잭션이 계속된다.
SUPPORTS메서드가 트랜잭션을 필요로 하지는 않지만, 진행 중인 트랜잭션이 존재 하면 트랜잭션을 사용한다는 것을 의미한다. 진행 중인 트랜잭션이 존재 하지 않더라도 메서드는 정상적으로 동작한다.
NOT_SUPPORTED메서드가 트랜잭션을 필요로 하지 않음을 의미한다. SUPPORTS와 달 리 진행 중인 트랜잭션이 존재할 경우 메서드가 실행되는 동안 트랜잭션 은 일시 중지되고 메서드 실행이 종료된 후에 트랜잭션을 계속 진행한다.
NEVER메서드가 트랜잭션을 필요로 하지 않는다. 만약 진행 중인 트랜잭션이 존 재하면 익셉션이 발생한다.
NESTED진행 중인 트랜잭션이 존재하면 기존 트랜잭션에 중첩된 트랜잭션에서 메서드를 실행한다. 진행 중인 트랜잭션이 존재하지 않으면 REQUIRED와 동일하게 동작한다. 이 기능은 JDBC 3.0 드라이버를 사용할 때에만 적 용된다(JTA Provider가 이 기능을 지원할 경우에도 사용 가능하다).

Isolation

Isolation 열거 타입에 정의된 값은 다음와 같다.

설명
DEFAULT기본 설정을 사용한다.
READ_UNCOMMITTED다른 트랜잭션이 커밋하지 않은 데이터를 읽을 수 있다.
READ_COMMITTED다른 트랜잭션이 커밋한 데이터를 읽을 수 있다.
REPEATABLE_READ처음에 읽어 온 데이터와 두 번째 읽어 온 데이터가 동일한 값을 갖는다.

rollbackFor

@Transactional 어노테이션의 rollbackFor 속성은 특정 예외가 발생했을 때 트랜잭션을 롤백하도록 지정하는 데 사용한다. 기본적으로 @Transactional은 RuntimeException 및 그 하위 클래스가 발생하면 롤백을 수행하지만, rollbackFor 속성을 사용하여 롤백할 예외를 구체적으로 지정할 수 있다.

@Transactional(rollbackFor = { Exception.class })
public void myMethod() {
    // your business logic here
}

위처럼 작성하면 모든 Exception에 대하여 롤백이 되게 된다.

profile
초코칩처럼 달콤한 코드를 짜자

0개의 댓글

관련 채용 정보