Transaction과 @Transactional

hongo·2023년 6월 8일
0
post-custom-banner

Transaction

Transaction이란 db에 접근하는 작업의 단위를 의미한다.

계좌 서비스가 있을 때 a가 b에게 송금하는 로직을 예시로 들어보자.

a가 b에게 10000원을 송금한다면, 아래의 두 로직으로 세분화할 수 있다.

  • a의 계좌에서 10000원을 차감
  • b의 계좌에 10000원을 추가

이 두 로직의 묶음, a가 b에게 송금하기 위한 작업의 단위를 트랜잭션이라고 본다.

Transaction의 특징 (ACID)

Transaction은 다음 네 가지의 특징을 가진다.

  • 원자성(Atomicity)
    • 트랜잭션의 연산은 전부 데이터베이스에 반영되던가, 전부 반영되지 않아야 한다. 일부분만 반영될 수는 없다.
  • 일관성(Consistency)
    • 트랜잭션을 하기 전과 후의 데이터베이스가 일관적이어야 함을 의미한다.
  • 독립성(Isolation)
    • 하나의 트랜잭션 실행중일 때 다른 트랜잭션이 연산에 끼어들 수 없음을 의미한다.
  • 지속성(Durability)
    • 트랜잭션이 성공적으로 이루어졌을 때, 그 결과가 영구적으로 지속되어야 함을 의미한다.

Commit과 Rollback

Commit은 트랜잭션을 데이터베이스에 영구적으로 반영하는 것을 의미한다.

Rollback은 Commit이전에 데이터 베이스의 상태를 트랜잭션이 수행되기 전의 상태로 되돌리는 것을 의미한다.

트랜잭션 도중에 에러가 발생한다면, 원자성에 따라 중간까지 반영된 트랜잭션이 전부 반영되지 않게 변경해야한다. 이 경우에 Rollback이 필요하다.

@Transactional

트랜잭션에 대한 처리를 하는 클래스, 메서드단에 @Transactional을 붙여 트랜잭션임을 선언할 수 있다.

@Transactional이 붙으면 프록시 객체를 생성해 트랜잭션에 대한 처리를 자동으로 수행한다.

보통 비즈니스 로직을 단위로 처리하는 서비스단에 @Transactional이 붙는다.

클래스와 메서드단에 전부 @Transactional이 붙어져있다면, 메서드단에 붙여진 @Transactional이 우선순위를 가진다.

@Transactional이 붙은 메서드는 도중에 uncheckedException 이 발생하면 Rollback을 수행한다.

@Transactional 의 옵션은 크게 다섯 가지가 존재한다.

  • isolation
    • 트랜잭션의 격리 수준을 설정
  • propagation
    • 트랜잭션의 전파 단계를 설정
  • noRollbackFor
    • 특정 예외 발생 시 rollback이 동작하지 않도록 설정
  • rollbackFor
    • 특정 예외 발생 시 rollback이 동작하도록 설정
  • timeout
    • 지정한 시간 내에 메소드 수행이 완료되지 않으면 rollback이 동작하도록 설정
  • readOnly
    • 트랜잭션을 읽기 전용으로 설정

이 중 이번 미션에서는 readOnly옵션을 사용해보았다. 이번 포스팅에서는 readOnly 옵션에 대해서만 정리해보도록 하겠다.

readOnly 옵션

트랜잭션이 INSERT, UPDATE, DELETE 명령어를 사용하지 않고, READ 명령어를 사용할 때 적용할 수 있는 옵션이다.

@Transactional
@Service
public class CartItemService {
    private final ProductRepository productRepository;
    private final CartItemRepository cartItemRepository;

    public CartItemService(ProductRepository productRepository, CartItemRepository cartItemRepository) {
        this.productRepository = productRepository;
        this.cartItemRepository = cartItemRepository;
    }

    @Transactional(readOnly = true) // 조회 로직에는 readOnly 옵션 설정
    public List<CartItemResponse> findByMember(Member member) {
        List<CartItem> cartItems = cartItemRepository.findCartItemsByMemberId(member.getId());
        ModelSortHelper.sortByIdInDescending(cartItems);
        return cartItems.stream().map(CartItemResponse::from).collect(Collectors.toList());
    }

    public Long add(Member member, CartItemRequest cartItemRequest) {
        Product product = productRepository.findById(cartItemRequest.getProductId());
        return cartItemRepository.add(new CartItem(member, product));
    }
    
    ...
        
}

이번 미션에서는 Mysql을 사용해 데이터베이스를 연동했는데, Mysql의 엔진인 InnoDB의 경우는 readOnly을 true로 설정하면 트랜잭션 ID를 설정하는 오버헤드를 피할 수 있다고 한다. 불필요한 트랜잭션 ID를 제거하면 조회 쿼리를 날릴 때 마다 참조되는 내부 데이터의 크기가 줄어든다고 한다. 참고 링크

트랜잭션 ID는 테이블의 각 행과 연관된 내부 필드로, INSERT, UPDATE, DELETE에 의해 변경되는 필드이며 Lock된 행을 기록한다고 한다. 여기서는 INSERT, UPDATE, DELETE 명령어를 할 때만 필요한 필드라고 이해하고 넘어가자.

h2의 경우에는 readOnly 옵션이 있어도 아무런 행동을 취하지 않는다고 하자. 자기가 사용하는 데이터베이스 엔진이 readOnly시 어떻게 작동하는 지를 파악해서 사용하도록 하자.

post-custom-banner

0개의 댓글