Spring 트랜잭션

반응형

Spring 트랜잭션

Spring Framework에서 제공하는 트랜잭션 관리 기능을 제공합니다.

(트랜잭션이란 데이터베이스나 다른 영속성 저장소에서 여러 개의 연산을 하나의 논리적 단위로 묶어서 원자성, 일관성, 격리성, 지속성을 보장하는 작업 단위입니다.)

 

Spring 트랜잭션

  • 선언적 트랜잭션 관리
    Spring은 `@Transactional` 어노테이션을 사용하여 선언적 트랜잭션 관리를 지원합니다. 이를 통해 개발자는 트랜잭션 관련 코드를 직접 작성하지 않고도 간편하게 트랜잭션을 사용할 수 있습니다.

  • 트랜잭션 경계 설정
    Spring은 트랜잭션 경계를 설정하여 트랜잭션의 시작과 종료를 자동으로 처리합니다. 경계 안에서 메서드가 정상적으로 종료되면 커밋하고, 예외가 발생하면 롤백합니다. 트랜잭션 경계를 설정하는 방법에는 어노테이션, XML 설정, AOP 등 다양한 방식이 있습니다.

  • 트랜잭션 속성 정의
    `@Transactional` 어노테이션을 사용하여 트랜잭션의 동작을 세밀하게 제어할 수 있습니다.
    isolation, propagation, readOnly, timeout 등의 속성을 설정하여 트랜잭션의 격리 수준, 전파 동작, 읽기 전용 여부, 제한 시간 등을 지정할 수 있습니다.

  • 다양한 트랜잭션 매니저 지원
    Spring은 다양한 트랜잭션 매니저를 지원합니다. JDBC, JPA, Hibernate, JTA 등 다양한 영속성 기술에 대한 트랜잭션 매니저를 제공하여 트랜잭션 관리를 일관되게 처리할 수 있습니다.

  • AOP 기반으로 동작
    선언적 트랜잭션은 Spring의 AOP (Aspect-Oriented Programming)를 기반으로 동작합니다. AOP는 프록시 객체를 사용하여 트랜잭션 관련 코드를 주입하고, 트랜잭션 경계를 설정합니다.

 


@Transactional 설정

방법1: XML

Spring 설정 파일에 `transactionManager` 빈을 등록. 일반적으로 DB 트랜잭션을 다루는 `DataSourceTransactionManager`를 사용합니다.

<!-- 트랜잭션 활성화: @Transactional 어노테이션 인식하도록 함 -->
<tx:annotation-driven transaction-manager="transactionManager" />

<!-- 데이터베이스 연결 정보를 설정하여 DataSource를 생성 -->
<bean id="dataSource" class="com.example.DataSource">
	<!-- ... -->
</bean>

<!-- DataSourceTransactionManager를 생성하여 트랜잭션 매니저로 등록 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"/>
</bean>

 

방법2:  Java 기반

`@EnableTransactionManagement` 어노테이션을 설정 파일에 추가하여 Spring의 트랜잭션 관리를 활성화

@Configuration
@EnableTransactionManagement         // 트랜잭션 관리 기능 활성화
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        // DB 연결 정보를 설정
        // ...
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        // DataSourceTransactionManager를 생성하여 트랜잭션 매니저로 등록
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource());
        return transactionManager;
    }
}

 

⭐`트랜잭션` 적용하려면 해당 메서드나 클래스는 빈으로 등록되어 있어야 합니다!!

 

 

PlatformTransactionManager 인터페이스는 트랜잭션 처리에 필요한 API를 제공해줍니다. 스프링 프레임워크는 다양한 환경과 제품에 대응하는 PlatformTransactionManager의 구현 클래스를 제공하는데 기본적으로 `DataSourceTransactionManager`를 사용합니다.

DataSourceTransactionManager JDBC 및 mybatis 등의 JDBC 기반 라이브버리로 DB 접근하는 경우 이용
HibernateTransactionManager Hibernate를 이용해 DB 접근하는 경우 이용
JpaTransactionManager JPA를 이용해 DB 접근하는 경우 이용
JtaTransactionManager JTA에서 트랜잭션을 관리하는 경우 이용
(글로벌 트랜잭션을 이용하는 경우)
WebLogincJtaTransactionManager 어플리케이션 서버인 웹로직의 JTA에서 트랜잭션을 관리하는 경우에 이용
WebSphereUowTransactionManager 어플리케이션 서버인 웹스피어의 JTA에서 트랜잭션을 관리하는 경우에 이용

 


@Transactional 사용 및 속성

@Service
public class AccountService {

    private final AccountRepository accountRepository;

    @Autowired
    public AccountService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    @Transactional
    public void transferMoney(String fromAccount, String toAccount, double amount) {
        try {
            // 출금 계좌에서 잔액 조회
            Account from = accountRepository.findByAccountNumber(fromAccount);
            double currentBalance = from.getBalance();

            if (currentBalance < amount) {
                throw new InsufficientBalanceException("Insufficient balance in the account.");
            }

            // 출금 계좌 돈 감소
            from.setBalance(currentBalance - amount);
            accountRepository.updateAccount(from);

            // 입금 계좌 돈 조회
            Account to = accountRepository.findByAccountNumber(toAccount);
            double toCurrentBalance = to.getBalance();

            // 입금 계좌 돈 증가
            to.setBalance(toCurrentBalance + amount);
            accountRepository.updateAccount(to);
        } catch (Exception e) {
            // 예외 발생 시 RollBack
            throw new TransferException("Failed to transfer money.", e);
        }
    }
}
  • `@Transactional` 어노테이션은 메서드 수준에 적용되어 해당 메서드가 트랜잭션 경계 내에서 실행됨을 나타냅니다.
  • 해당 메서드가 실행 전에 트랜잭션이 시작되고, 메서드 실행 후에 커밋 또는 롤백이 수행됩니다.
  • 예외가 발생한 경우(`TransferException`) Rollback됩니다.

 


클래스 수준에 `@Transactional` 적용

  • `@Transactional` 어노테이션은 클래스 수준에도 적용될 수 있습니다. 아래 처럼 `AccountService` 클래스에 적용할 경우 `AccountService` 클래스의 모든 메서드에 트랜잭션 설정이 적용됩니다. 메서드 수준에서 `@Transactional`을 부여하였다면 클래스에 부여한 것보다 우선해서 적용됩니다.
@Service
@Transactional
public class AccountService {

    private final AccountRepository accountRepository;

    @Autowired
    public AccountService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    public void transferMoney(String fromAccount, String toAccount, double amount) {
        // 이체 로직
    }

    public void withdrawMoney(String accountNumber, double amount) {
        // 출금 로직
    }

    public void depositMoney(String accountNumber, double amount) {
        // 입금 로직
    }
}

예외 발생시 RollBack 여부 지정

Spring Framework의 `@Transactional` 어노테이션은 `RuntimeException`과 그 하위 예외들이 발생하면 RollBack을 수행합니다. `RuntimeException`예외는 실행 시 발생하는 예외로 다음과 같은 예외가 있습니다.

  • NullPointerException / ArrayIndexOutOfBoundsException, IllegalArgumentException, ...

즉, 위에서 예시로 들었던 코드의 `TransferException`도 다음과 같이 정의되면 `RuntimeException`의 하위 예외이기 때문에 `TransferException`이 발생하면 트랜잭션은 RollBack 됩니다.

public class TransferException extends RuntimeException {
    // ...
}

 

`RuntimeException`의 하위 예외가 아니어도 예외가 발생시 RollBack을 발생하게 할 수 있고 `RuntimeException`의 하위 예외여도 예외 발생시 Rollback을 발생하지 않게 할 수 있습니다. 다음과 같이 `rollbackFor` 속성과 `noRollbackFor` 속성을 사용하면 됩니다.

@Transactional(rollbackFor = { CustomException1.class }, noRollbackFor = { CustomException2.class })
public void method() {
    // ...
}

위 예제 코드에서는, `CustomException1`이 발생하면 RollBack되고, `CustomException2`이 발생하도 RollBack되지 않습니다.

 


전파 방식: propagation

@Transactional 어노테이션의 `propagation 속성`은 트랜잭션의 전파 방식을 지정하는 데 사용됩니다. 트랜잭션의 전파 방식은 메서드 내에서 이미 실행 중인 트랜잭션이 있는 경우, 새로운 트랜잭션이 시작될 때 어떻게 동작할지를 결정합니다.


propagation 속성

  • `Propagation.REQUIRED` (Default)
    • 메소드를 수행하는데 트랜잭션이 필요하다는 것을 의미
    • 현재 진행중인 트랜잭션이 존재하면 해당 트랜잭션을 사용
    • 존재하지 않으면 새로운 트랜잭션을 생성
  • `Propagation.REQUIRES_NEW`
    • 항상 새로운 트랜잭션을 시작한다.
    • 진행 중인 트랜잭션이 있을 경우 기존 트랜잭션을 일시 중지하고 새로운 트랜잭션을 시작
    • 새로 시작된 트랜잭션이 종료된 뒤에 기존 트랜잭션이 계속 진행됨.
  • `Propagation.SUPPORTS`
    • 메소드가 트랜잭션을 필요로 하지 않지만,
    • 진행 중인 트랜잭션이 존재하면 해당 트랜잭션에 참여
    • 진행 중인 트랜잭션이 존재하지 않으면 트랜잭션이 없이 실행
  • `Propagation.NOT_SUPPORTED`
    • 메소드가 트랜잭션을 필요로 하지 않는다는 의미
    • 진행 중인 트랜잭션이 존재할 경우 해당 트랜잭션을 일시 중지시키고, 트랜잭션 없이 실행시킵니다. 실행이 종료된 후에 정지 시킨 트랜잭션을 계속 진행합니다.
  • `Propagation.MANDATORY`
    • 메소드를 수행하는데 트랜잭션이 필요하다는 것을 의미
    • 진행 중인 트랜잭션이 존재하면 해당 트랜잭션에 참여
    • 진행 중인 트랜잭션이 존재하지 않을 경우 예외가 발생
  • `Propagation.NEVER`
    • 메소드가 트랜잭션을 필요로 하지 않는다는 의미
    • 진행 중인 트랜잭션이 존재하면 예외가 발생
    • 진행 중인 트랜잭션이 존재하지 않을 경우 트랜잭션 없이 실행
  • `Propagation.NESTED`
    • 메소드를 수행하는데 트랜잭션이 필요하다는 것을 의미
    • 진행 중인 트랜잭션이 존재하면 기존 트랜잭션에 중첩된 트랜잭션에서 메소드를 실행
    • 진행 중인 트랜잭션이 존재하지 않으면 새로운 트랜잭션을 생성
    • JDBC 3.0 드라이버를 사용할 때만 적용
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method() {
    // ...
}

 

위의 예제에서는 항상 새로운 트랜잭션을 시작하여 method() 메서드를 실행합니다. 즉, 이미 실행 중인 트랜잭션이 있다면 일시 중지시키고 새로운 트랜잭션을 시작합니다.

 

트랜잭션의 전파 방식은 다른 서비스 메서드를 호출하거나 동일한 클래스 내에서 다른 @Transactional 메서드를 호출할 때 유용하게 활용됩니다. 이를 통해 여러 메서드 간에 트랜잭션의 경계를 조절하고, 원하는 트랜잭션 동작을 구현할 수 있습니다.

 


격리 수준: isolation

@Transactional 어노테이션의 isolation 속성은 트랜잭션의 격리 수준을 지정하는 데 사용됩니다. 

격리 수준은 동시에 DB에 접근할 때 그 접근을 어떻게 제어할지에 대한 설정입니다.

 

isolation 속성

  • `Isolation.DEFAULT`
    • 데이터베이스의 기본 격리 수준을 사용합니다.
    • 일반적으로 데이터베이스 벤더의 기본값으로 설정됩니다.
  • `Isolation.READ_UNCOMMITTED`
    • 커밋되지 않은 데이터에 대한 읽기를 허용합니다.
    • 다른 트랜잭션에서 수정 중인 데이터를 읽을 수 있으며, 이로 인해 "Dirty Read"와 "Non-Repeatable Read" 문제가 발생할 수 있습니다.
  • `Isolation.READ_COMMITTED`
    • 커밋된 데이터만 읽을 수 있습니다.
    • 다른 트랜잭션에서 수정 중인 데이터는 읽을 수 없지만, 다른 트랜잭션에서 커밋한 데이터는 읽을 수 있습니다. "Dirty Read" 문제는 발생하지 않지만, "Non-Repeatable Read" 문제는 여전히 발생할 수 있습니다.
  • `Isolation.REPEATABLE_READ`
    • 한 번 읽은 데이터는 트랜잭션 종료까지 동일한 값을 보장합니다.
    • 즉, 처음에 읽어 온 데이터와 두 번째 읽어온 데이터가 동일합니다.
    • 다른 트랜잭션에서 수정 중인 데이터를 읽지 않으며, "Dirty Read"와 "Non-Repeatable Read" 문제가 발생하지 않습니다. 하지만 "Phantom Read" 문제는 여전히 발생할 수 있습니다.
  • `Isolation.SERIALIZABLE`
    • 같은 데이터에 대해서 한 개의 트랜잭션만 수행 가능합니다.
    • 즉, 동시에 실행되는 트랜잭션이 서로 영향을 미치지 않도록 보장합니다.
    • "Dirty Read", "Non-Repeatable Read", "Phantom Read" 문제가 발생하지 않지만, 동시성이 감소될 수 있습니다.
반응형