@Transactional

최준영·2022년 10월 9일
0
post-custom-banner

1. 트랜잭션이란

  • 데이터베이스에서 하나의 논리적 기능을 수행하기 위한 작업의 단위를 말한다.
    • A 계좌에서 B계좌로 만원을 이체한다. 기능을 실현하기 위해서는 A 계좌에서 만원을 인출하는 UPDATE문B 계좌로 만원을 입금하는 UPDATE문 이라는 작업을 필요로 한다.
  • commit
    • 트랜잭션을 종료하기 위해 사용하는 명령어
  • rollback
    • 트랜잭션 중간에 작업이 잘못되어, 트랜잭션 시작 이전으로 되돌리는 명령어

2. 자바에서 트랜잭션을 다루는 방법

import java.sql.Connection;

Connection connection = dataSource.getConnection();

try (connection) {
    connection.setAutoCommit(false);
    // 비즈니스 로직
    connection.commit();

} catch (SQLException e) {
    connection.rollback();
} finally {
		connection.close();
}
  • JDBC로 트랜잭션을 다루는 방법이다.
  • 자바에서 데이터베이스의 트랜잭션을 시작하는 유일한 방법이다.
    • 스프링의 @Transactional도 내부적으로는 동일하게 동작한다.
  • 동작 흐름
    1. dataSource를 통해 db와 연결한다.
    2. connection.setAutoCommit(false) 로 자동 커밋이 되지 않도록 한다.
    3. 로직을 실행하고 커밋한다.
    4. 실행 도중에 예외가 발생한 경우 롤백한다.

3. Spring에서 트랜잭션을 다루는 방법

1) 프로그래밍 방식의 트랜잭션

  • 스프링은 두 가지 방식의 트랜잭션 구현 방법을 제공한다.
    • Transaction Template을 사용하는것을 권장
  • JDBC 방식과의 비교
    • DB 커넥션을 직접 열고 닫을 필요가 없다.
    • 스프링이 SQLException을 RunTimeException으로 변환해준다.

TransactionTemplate

@Service
public class UserService {

    @Autowired
    private TransactionTemplate template;

    public Long registerUser(User user) {
        Long id = template.execute(status ->  {
            // SQL 실행
            // ex) inserts the user into the db and returns the autogenerated id
            return id;
        });
    }
}

PlatformTransactionManager 구현체 사용

public class PaymentService {

    private PlatformTransactionManager transactionManager;

    public void setTransactionManager(PlatformTransactionManager transactionManager){
        this.transactionManager = transactionManager;
    }

    public void transactionCode() throws Exception {
        TransactionStatus status = this.transactionManager.getTransaction(new DefaultTransactionDefinition());

        try {
            // execute some SQL statements... 
            this.transactionManager.commit(status);
        } catch (RuntimeException e) {
            this.transactionManager.rollback(status);
            throw e;
        }
    }
}

2) 선언적 트랜잭션 - @Transactional 사용

public class UserService {

    @Transactional
    public Long registerUser(User user) {
        // execute some SQL that e.g.
        // inserts the user into the db and retrieves the autogenerated id
        // userDao.save(user);
        return id;
    }
}
  • 일반적으로 사용하는 방식이다.
    • 비즈니스 로직과 트랜잭션 처리 로직을 분리하여 코드 중복을 줄이고, 편리하게 사용할 수 있기 때문이다.
  • 메서드에 @Transactional을 붙이면, 빈을 등록할 때 해당 메서드를 오버라이딩한 프록시 객체가 생성되어 원본 객체 대신 빈으로 등록된다.

주의점

  • 어떤 객체가 A와 B라는 메서드를 가지고 있고 B 메서드에는 @Transactional이 적용되어 있다고 하자. 이때, A 메서드가 B 메서드를 호출하는 경우 @Transactional이 적용되지 않는다.
    • 스프링 AOP는 실제 대상 코드에 트랜잭션 코드를 붙이는 방식이 아닌, 메서드 오버라이딩을 통해 트랜잭션 처리를 적용하는 방식으로 프록시를 생성하기 때문이다. 따라서 트랜잭션이 적용된 상태로 동작하려면 프록시를 통해 접근해야 한다. 하지만 위의 방식은 실제 대상 코드를 호출하였기 때문에 트랜잭션이 적용되지 않는다.
    • 코드
      public class BooksImpl implements Books {
      
              public void addBooks(List<String> bookNames) {
                      bookNames.forEach(bookName -> this.addBook(bookName));
              }
      
              @Transactional
              public void addBook(String bookName) {
                      Book book = new Book(bookName);
                      bookRepository.save(book);
                      book.setFlag(true);
              }
      }
  • 대상 메서드를 오버라이딩 하는 방식으로 프록시를 생성한다. 하지만 private 메서드는 오버라이딩을 할 수 없기 때문에 @Transactional을 붙여도 트랜잭션이 동작하지 않는다.
  • @Transactional이 붙은 메서드가 다른 클래스의 @Transactional이 붙은 메소드를 호출하는 경우, 트랜잭션 전파 속성에 따라 트랜잭션이 동작한다.

트랜잭션 전파 속성

  • 트랜잭션을 시작하거나 기존 트랜잭션에 참여하는 방법을 결정하는 속성

  • REQUIRED

    • 기본 전파 속성이다.
    • 이미 진행 중인 트랜잭션이 없으면 새로 시작하고, 진행 중인 트랜잭션이 있다면 기존 트랜잭션에 참여한다.
  • REQUIRED_NEW

    • 항상 새로운 트랜잭션을 시작한다. 진행 중인 트랜잭션이 있다면 보류하고 새로운 트랜잭션을 진행한다.
    • 기존의 트랜잭션 유무와 상관 없이, 새로운 트랜잭션을 만들어 독립적으로 동작한다.
  • MANDATORY

    • 이미 진행중인 트랜잭션이 있으면 해당 트랜잭션에 합류한다.
    • 진행중인 트랜잭션이 없다면 예외를 발생시킨다.
  • NESTED

    • 이미 진행 중인 트랜잭션이 존재한다면, 중첩 트랜잭션을 만든다.
      • 중첩 트랜잭션이란 트랜잭션 내부에 자식 트랜잭션을 만드는 것이다.
      • 독립적으로 동작하는 REQUIRED_NEW와 달리, 부모 트랜잭션의 커밋, 롤백에 영향을 받지만 자신의 커밋과 롤백은 부모 트랜잭션에 영향을 주지 못한다.
  • NEVER

    • 트랜잭션을 사용하지 않는다. 트랜잭션이 존재한다면 예외를 발생한다.
  • SUPPORTS

    • 이미 진행 중인 트랜잭션이 있다면 해당 트랜잭션에 참여한다. 이미 진행 중인 트랜잭션이 없다면 트랜잭션 없이 진행한다.
  • NOT_SUPPORTED

    • 트랜잭션을 사용하지 않는다. 진행 중인 트랜잭션이 있다면 보류하고 트랜잭션 없이 진행한다.
  • 참고

profile
do for me
post-custom-banner

0개의 댓글