[Spring]그림으로 배우는 스프링 6 - 9장 선언적 트랜잭션

Gaeng·2024년 11월 18일

[Spring] 공부

목록 보기
9/21
post-thumbnail

책의 내용을 기반으로 내용을 추가하고 정리하였습니다.

Transactoin?

트랜잭션

트랜잭션은 여러 개의 연관된 처리를 하나의 처리로 다룰 때 단위로 사용되는 용어.

DB트랜잭션?

DB 트랜잭션(Transaction)은 데이터베이스의 상태를 일관되게 유지하기 위해 사용하는 작업 단위입니다. 트랜잭션은 데이터베이스 작업을 안전하게 처리하기 위해 ACID 특성을 준수해야 합니다.

특성설명예시
Atomicity (원자성)트랜잭션은 모두 실행되거나, 전혀 실행되지 않아야 합니다.
중간에 오류 발생 시 모든 작업은 롤백(Rollback)되어 원래 상태로 복구됩니다.
송금 시, 송신 계좌의 출금과 수신 계좌의 입금이 둘 다 성공하거나 둘 다 실패해야 함.
Consistency (일관성)트랜잭션 실행 전후에 데이터베이스는 일관된 상태를 유지해야 합니다.
데이터 무결성이 보장되어야 하며 규칙이 깨지지 않도록 해야 합니다.
은행 계좌 잔액의 합이 트랜잭션 전후 동일해야 함.
Isolation (고립성)트랜잭션 간의 작업이 서로 간섭하지 않아야 합니다.
한 트랜잭션 완료 전, 다른 트랜잭션은 해당 작업의 중간 상태를 볼 수 없습니다.
고립 수준: READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE.
다중 사용자 환경에서 각 사용자의 거래가 독립적으로 처리됨.
Durability (지속성)트랜잭션이 성공적으로 완료되면, 그 결과는 영구적으로 저장됩니다.
시스템 장애가 발생해도 결과는 손실되지 않습니다.
송금 완료 후 시스템 장애가 발생해도 거래 내역이 데이터베이스에 남아 있어야 함.

DB 트랜잭션 흐름도

  • START TRANSACTION
    트랜잭션을 시작합니다.
  • 쿼리 실행
    순차적으로 SQL 작업을 실행합니다.
  • 성공 확인
    쿼리가 성공했는지 확인하며, 실패 시 롤백으로 이동합니다.
    COMMIT 또는 ROLLBACK

Web Application's Transaction

업무 로직 메서드가 호출되는 시점에 트랙잭션을 시작하고, 업무 로직 메서드가 종료되는 시점에 트랜잭션을 종료하는 것이 가장 많이 사용되는 예

트랜잭션과 커넥션?

트랜잭션을 자체적으로 제어할 때의 문제점?

  • 직접 트랜잭션 제어를 코딩할 수 있지만, 업무 로직에 트랜잭션에 제어 처리가 섞여 코드 가독성이 낮아짐
  • 트랜잭션 제어 코드를 하는 코드는 뻔한 중복 코드인데, 업무 로직 메서드 전체에 작성해야함.
      public class AccountService {
      private static final String DB_URL = "jdbc:mysql://localhost:3306/your_database";
      private static final String DB_USER = "your_username";
      private static final String DB_PASSWORD = "your_password";

      public void transferMoney(String fromAccount, String toAccount, int amount) {
          Connection connection = null;

          try {
              // 1. 데이터베이스 연결
              connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);

              // 2. 트랜잭션 시작 (자동 커밋 끄기)
              connection.setAutoCommit(false);

              // 3. 계좌 출금
              try (PreparedStatement withdrawStmt = connection.prepareStatement(
                      "UPDATE accounts SET balance = balance - ? WHERE account_id = ?")) {
                  withdrawStmt.setInt(1, amount);
                  withdrawStmt.setString(2, fromAccount);
                  withdrawStmt.executeUpdate();
              }

              // 4. 계좌 입금
              try (PreparedStatement depositStmt = connection.prepareStatement(
                      "UPDATE accounts SET balance = balance + ? WHERE account_id = ?")) {
                  depositStmt.setInt(1, amount);
                  depositStmt.setString(2, toAccount);
                  depositStmt.executeUpdate();
              }

              // 5. 트랜잭션 커밋
              connection.commit();

          } catch (SQLException e) {
              // 오류 발생 시 롤백
              if (connection != null) {
                  try {
                      connection.rollback();
                  } catch (SQLException rollbackEx) {
                      rollbackEx.printStackTrace();
                  }
              }
              throw new RuntimeException("Transaction failed: " + e.getMessage(), e);
          } finally {
              // 연결 닫기
              if (connection != null) {
                  try {
                      connection.setAutoCommit(true); // 원래 상태로 복구
                      connection.close();
                  } catch (SQLException closeEx) {
                      closeEx.printStackTrace();
                  }
              }
          }
      }
  }

스프링의 선언적 트랜잭션

@Transactional

  • 선언적 트랜잭션 : 개발자가 명시적으로 트랜잭션 제어를 하는 것이 아닌, 트랜잭션 제어를 하고 싶다고 어노테이션 선언하면 끝
  • 업무 로직 메서드에 @Transactional을 선언하면 자동으로 트랜잭션 제어가 이루어짐.
  • 자동으로 커넥션 획득 및 시작되며 메서도가 자동 종료 됨으로 커넥션이 닫힘
  • 예외처리 발생시 자동 롤백

장점

  • 간결한 코드: 어노테이션을 사용하면 트랜잭션 관리 코드가 간단하고 직관적입니다.
  • 자동 롤백: 예외 발생 시 자동으로 롤백되므로 데이터 무결성을 쉽게 유지할 수 있습니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BankTransferService {

    private final AccountRepository accountRepository;

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

    // @Transactional 어노테이션으로 트랜잭션을 선언적 방식으로 처리
    @Transactional
    public void transferMoney(String fromAccount, String toAccount, int amount) {
        // 출금: fromAccount에서 금액을 차감
        accountRepository.withdraw(fromAccount, amount);

        // 입금: toAccount에 금액을 추가
        accountRepository.deposit(toAccount, amount);
    }
}

트랜잭션 내부구조?

선언적 트랜잭션이 선언되면, Spring은 Proxy 객체를 생성하여 메서드 호출 전후에 트랜잭션을 관리합니다. Proxy는 데이터베이스와의 연결을 설정하고, 메서드 실행 중 발생하는 오류에 따라 트랜잭션을 커밋하거나 롤백합니다.

  • proxy는 객체의 실제 기능을 대리하는 객체로, 주로 AOP (Aspect-Oriented Programming)에서 사용됩니다.

PlatformTransactionManager (인터페이스)

  • PlatformTransactionManager는 Spring에서 트랜잭션을 관리하는 상위 인터페이스입니다. 트랜잭션을 관리하는 공통적인 API를 정의하며, 다양한 트랜잭션 관리 방법을 제공할 수 있도록 합니다.
  • 한 트랜잭션 관리 방법을 지원하기 위해 다양한 구현체가 존재합니다. 예를 들어, JDBC, JPA, Hibernate 등을 사용할 때 각각의 구현체가 다르게 동작합니다.

이미지 출처 : 그림으로 배우는 스프링6

profile
문제를 해결하면서 나온 문제를 기록하는 노트

0개의 댓글