이번 글에서는 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차 캐시에 없을 경우 DB에서 조회 (1차 캐시에 저장 후 반환)
저장 시 동작 방식
- 쓰기 지연 SQL 저장소에 모으기 (성능 향상을 위해)
- 트랜잭션을 커밋하는 순간,
쓰기 지연 저장소
의 쿼리를 한번에 디비에 SQL을 보냄
수정 시 동작 방식
- 플러시 시점에 엔티티와 스냅샷(엔티티를 영속성 컨텍스트에 보관할 때의 최초 상태)을 비교
- 바뀐 엔티티가 있으면 update 쿼리를 쓰기 지연 저장소에 저장
- 트랜잭션을 커밋하는 순간,
쓰기 지연 저장소
의 쿼리를 한번에 디비에 SQL을 보냄
2. @Transactional 동작
@Transactional은 Spring AOP로, 프록시 패턴으로 동작합니다.
- Spring은
@Transactional이 붙은 메소드
에 대한 프록시 객체를 생성한다. @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
를 해주어서 쿼리 성능을 향상시켜줍니다.