이번 글에서는 JPA 사용 시, 트랜잭션을 만들기 위해 사용했던 @Transaction에 대해 알아보겠습니다. 알아야 할 부분과 헷갈렸던 부분을 위주로 정리해보았습니다.

@Transaction이란

우선 트랜잭션이란, DB의 상태를 변화시키는 최소 작업 단위를 뜻합니다. 구체적으로는 조회, 삽입, 삭제 등을 수행하는 쿼리 모음입니다. 이 트랜잭션을 생성하는 데 @Transaction 어노테이션을 사용해줄 수 있습니다.

@Transaction을 메소드(혹은 클래스, 인터페이스) 위에 붙이면, 해당 메소드가 하나의 트랜잭션이 됩니다. 이 @Transaction을 선언적 트랜잭션이라고 합니다.

트랜잭션은 다음을 만족하게 됩니다! 간단하게는, 해당 메소드를 DB에 반영하고, 중간에 누가 끼지 못하고, 그 범위 안에서의 Exception이 발생하면 rollback을 해주기 위해 사용합니다.

  • Atomicity(원자성): 트랜잭션의 작업들은 전부 수행되거나 아예 수행되지 않아야 합니다.
    • 해당 트랜잭션에서 에러가 발생하면, 트랜잭션이 롤백됩니다.
  • Consistency(일관성): 트랜잭션 정상 실행 후, 데이터베이스의 스키마에 변화가 없어야 합니다.
  • Isolation(고립성): 트랜잭션들 끼리 서로의 작업에 끼어들지 못합니다.
    • 해당 메소드 시작~종료까지는 다른 트랜잭션이 끼어들지 못한다.
  • Durability(지속성): 정상적으로 수행된 트랜잭션은 영원히 DB에 반영되어야 합니다.

참고로 @Transaction을 메소드에 붙이면 메소드가 하나의 트랜잭션이 되고, 클래스에 붙이면 클래스 내부의 모든 메소드가 하나의 트랜잭션에서 실행됩니다. 참고로 클래스와 메소드에 모두 @Transaction이 붙으면, 메소드가 더 높은 우선 순위를 가집니다.

Spring의 @Transactional 동작 방식

@Transactional의 작동 방식을 설명하기 위해서 영속성 컨텍스트에 대해 간단히 정리해보겠습니다.

1. 영속성 컨텍스트

영속성 컨텍스트는 애플리케이션 - DB 사이에서 객체를 보관하는 가상의 DB 역할을 합니다(플러시 시점에 DB에 반영). 이는 캐싱을 위해서 사용합니다.

조회 시의 동작 방식
  1. 1차 캐시에서 조회
  2. 1차 캐시에 없을 경우 DB에서 조회 (1차 캐시에 저장 후 반환)

img

저장 시 동작 방식
  1. 쓰기 지연 SQL 저장소에 모으기 (성능 향상을 위해)
  2. 트랜잭션을 커밋하는 순간, 쓰기 지연 저장소의 쿼리를 한번에 디비에 SQL을 보냄
수정 시 동작 방식
  1. 플러시 시점에 엔티티와 스냅샷(엔티티를 영속성 컨텍스트에 보관할 때의 최초 상태)을 비교
  2. 바뀐 엔티티가 있으면 update 쿼리를 쓰기 지연 저장소에 저장
  3. 트랜잭션을 커밋하는 순간, 쓰기 지연 저장소의 쿼리를 한번에 디비에 SQL을 보냄

2. @Transactional 동작

@Transactional은 Spring AOP로, 프록시 패턴으로 동작합니다.

  1. Spring은 @Transactional이 붙은 메소드에 대한 프록시 객체를 생성한다.
  2. @Transactional이 붙은 메소드 앞/뒤에 트랜잭션 로직이 추가됩니다.

@Transactional은 내부적으로는 JDBC 연결하고 메소드 수행 후, 커밋과 플러시되어 DB에 정상 반영됩니다. 따라서 만약 @Transactional이 없으면, 커밋이 되지 않고 DB에 반영되지 않습니다.

JPA와 @Transactional(readOnly = true)

JpaRepository의 메소드를 확인해보면, 다음처럼 @Transactional이 선언되어 있습니다. 이때, 기능별로 옵션이 다르게 설정되어있습니다. 클래스 전체에는 @Transactional(readOnly = true), save와 delete 메소드에는 @Transactional(옵션 따로 설정 안해주면 readOnly = false)으로 설정되어 있습니다. 즉,

  • save와 delete 메소드 : @Transactional(readOnly = false)
  • find, get 등 조회 메소드 : @Transactional(readOnly = true)

readOnly = true 설정을 해줄 경우, 플러시가 발생하지 않습니다. 즉, 커밋하더라도 해당 쿼리가 DB에 반영되지 않습니다. 또한 변경 감지(더티 체크)를 위한 스냅샷을 저장하지 않습니다.

따라서 조회만 해올 경우에는 readOnly = true 를 해주어서 쿼리 성능을 향상시켜줍니다.

img