@Transactional 개념 및 작동 방식

젼이·2024년 10월 29일

@Transactional 개념 및 작동 방식

@Transactional은 Spring Framework에서 제공하는 트랜잭션 관리를 위한 애너테이션이다.
데이터베이스와의 작업이 완벽하게 수행 되도록 보장하기 위해 사용되며, 모든 작업이 성공하거나 아니면 모두 롤백되도록 한다.


예를 들어, 데이터베이스에 여러 레코드를 추가하는 작업 중 하나라도 실패한다면, 전체 작업을 롤백하여 이전 상태로 복구한다. 이렇게 하면 데이터 일관성을 유지할 수 있다.





트랜잭션의 주요 특징

  1. 원자성 (Atomicity): 트랜잭션에 포함된 작업들은 모두 성공하거나 모두 실패한다.
  2. 일관성 (Consistency): 트랜잭션이 성공적으로 완료되면 데이터베이스는 유효한 상태를 유지한다.
  3. 격리성 (Isolation): 트랜잭션 간의 작업이 서로 영향을 주지 않도록 보장한다.
  4. 내구성 (Durability): 트랜잭션이 성공적으로 완료되면 그 결과는 영구적으로 저장된다.




@Transactional의 기본 작동 방식

  • 트랜잭션 시작: 메서드가 호출될 때 트랜잭션이 시작된다.
  • 성공적인 종료: 메서드가 예외 없이 정상적으로 끝나면 커밋(commit)이 되어 DB에 반영된다.
  • 예외 발생 시 롤백: 메서드가 실행 도중 런타임 예외가 발생하면 롤백(rollback) 되어 작업 전 상태로 되돌린다.




예제: @Transactional을 사용하는 이유

아래는 은행 송금 예제이다. 사용자가 A 계좌에서 B 계좌로 100원을 송금할 때, 두 가지 작업이 필요하다.


1. A 계좌에서 100원을 출금한다.
2. B 계좌에서 100원을 입금한다.

예제 코드

1. Service 코드(송금 로직)

@Service
@RequiredArgsConstructor
public class BankService {

    private final AccountRepository accountRepository;

    // 이 메서드에 @Transactional을 사용하여 송금 도중 문제가 생기면 전체 작업을 롤백합니다.
    @Transactional
    public void transferMoney(Long fromAccountId, Long toAccountId, int amount) {
        // A 계좌에서 출금
        Account fromAccount = accountRepository.findById(fromAccountId)
                .orElseThrow(() -> new IllegalArgumentException("출금 계좌를 찾을 수 없습니다."));
        fromAccount.withdraw(amount);  // 금액 차감

        // B 계좌에 입금
        Account toAccount = accountRepository.findById(toAccountId)
                .orElseThrow(() -> new IllegalArgumentException("입금 계좌를 찾을 수 없습니다."));
        toAccount.deposit(amount);  // 금액 추가

        // 변경된 계좌 정보를 저장
        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);
    }
}

2. Account 엔티티

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Account {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String owner;
    private int balance;

    // 출금 메서드
    public void withdraw(int amount) {
        if (this.balance < amount) {
            throw new IllegalArgumentException("잔액이 부족합니다.");
        }
        this.balance -= amount;
    }

    // 입금 메서드
    public void deposit(int amount) {
        this.balance += amount;
    }
}

3. Repository 코드

public interface AccountRepository extends JpaRepository<Account, Long> {
}

작동 흐름

  1. transferMoney() 메서드가 호출되면 트랜잭션이 시작된다.
  2. A 계좌에서 출금하고 B 계좌에 입금한다.
  3. 만약 출금 중 잔액 부족 예외가 발생하면, 전체 작업이 롤백된다.
  4. 예외가 발생하지 않으면 트랜잭션이 커밋되고, 두 계좌의 변경사항이 DB에 반영된다.

예외 처리와 롤백

  • 롤백 발생 조건: 기본적으로 RuntimeException 또는 그 하위 예외가 발생하면 롤백된다.
  • 체크 예외 발생 시 기본적으로 롤백되지 않지만, 원하는 경우 직접 설정할 수 있다.
@Transactional(rollbackFor = Exception.class)  // 모든 예외 발생 시 롤백
public void transferMoney(Long fromAccountId, Long toAccountId, int amount) {
    // 송금 로직
}




왜 @Transactional이 필요한가?

  1. 데이터 일관성 보장: 여러 DB 작업을 하나의 트랜잭션으로 묶어 부분 실패를 방지한다.
  2. 예외 발생 시 자동 롤백: 런타임 예외가 발생하면 트랜잭션이 자동으로 롤백된다.
  3. 읽기 전용 트랜잭션: 읽기 전용 메서드에 @Transactional(readOnly = true) 를 사용하면 성능 최적화가 가능하다.




@Transactional(readOnly = true) 사용 이유

읽기 전용 트랜잭션에서는 DB에 대한 변경 작업이 허용되지 않는다.
Hibernate는 이 설정을 통해 성능을 최적화한다.
주로 조회 전용 메서드에 사용한다.

@Transactional(readOnly = true)
public List<Account> getAllAccounts() {
    return accountRepository.findAll();
}




정리

  • @Transactional은 데이터베이스와의 여러 작업을 하나의 트랜잭션으로 묶어 처리한다.
  • 런타임 예외가 발생하면 롤백되고, 모든 작업이 성공하면 커밋된다.
  • 읽기 전용 트랜잭션을 사용하면 성능이 최적화된다.
profile
신입 개발자 임니당 : > (2025.02.05~)

0개의 댓글