[SpringBoot] JPA Transaction

김선형·2025년 9월 11일

Java

목록 보기
26/27

개요

JPA의 변경 내용 (저장/수정/삭제)은 트랜잭션이 있을 때만 데이터베이스에 안전하게 반영된다.
JPA에서는 서비스 계층에서 트랜잭션을 시작하며, Spring은 @Transactional 어노테이션을 사용해 트랜잭션을 자동으로 관리한다.

JPA 트랜잭션 Commit 시점

기본적으로 트랜잭션을 시작한 메서드가 성공적으로 종료될 때 Commit된다. Spring 환경에서는 @Transactional 어노테이션이 붙은 메서드가 예외 없이 실행을 마치는 시점에 Commit이 발생한다.

@Transactional

트랜잭션 관리를 지원하는 선언적 어노테이션이다. 메서드/클래스에 붙이면, 해당 범위 내의 모든 데이터베이스 작업을 트랜잭션 단위로 처리한다. 모든 작업이 성공하면 Commit, 중간에 예외가 발생하면 자동으로 Rollback 처리된다.

속성기본값설명
propagationREQUIRED트랜잭션 전파 방식 (REQUIRED, REQUIRES_NEW, etc.)
isolationDEFAULT트랜잭션 격리 수준 (READ_COMMITTED, REPEATABLE_READ, etc.)
readOnlyfalse읽기 전용 트랜잭션으로 설정 (삽입/수정/삭제 없이 조회만 하는 경우 성능 향상)
timeout-1트랜잭션 제한 시간 (초), 초과 시 Rollback
rollbackFor{}지정한 예외 발생 시에만 Rollback
(Default는 RuntimeException, Error 발생 시 Rollback)
noRollbackFor{}지정한 예외 발생 시에는 Rollback하지 않음

설정 기준

DB 변경이 있는 오퍼레이션을 수행하는 서비스 메서드에는 @Transactional을 사용하는 것을 권장한다. 그러나 남용하는 경우, 트랜잭션 범위가 넓어져 DB lock, 동시성 저하 등 성능 문제가 생길 수 있다.

예시

  • 데이터 변경 (INSERT/UPDATE/DELETE): 반드시 사용한다.
  • 단순 조회 (SELECT): @Transactional(readOnly = true)를 사용하거나 사용하지 않는다.
  • 외부 API/파일 등 장시간 IO 포함: 트랜잭션 범위를 DB 오퍼레이션만 포함하도록 최대한 좁게 유지한다.

트랜잭션 전파 (Propagation)

트랜잭션이 이미 존재할 때, 현재 실행 중인 메서드에서 트랜잭션을 어떻게 처리할지 결정한다.

옵션명설명사용 예시
REQUIRED (default)기존 트랜잭션이 있으면 참여하고, 없으면 새로 생성한다.대부분의 서비스 로직
REQUIRES_NEW항상 새로운 트랜잭션을 생성하며, 기존 트랜잭션은 일시중단한다.로그 기록, 감사
SUPPORTS트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행한다.조회성 로직
MANDATORY반드시 트랜잭션 내에서만 실행하고, 없으면 예외가 발생한다.보안 로직
NOT_SUPPORTED트랜잭션이 있으면 일시 중단하고, 트랜잭션 없이 실행한다.외부 시스템 연동, 대용량 데이터 연동
NEVER트랜잭션 없이만 실행하고, 트랜잭션이 있으면 예외가 발생한다.트랜잭션과 같이 동작하면 안 되는 로직
NESTED기존 트랜잭션이 있으면 중첩 (새 savepoint)하고,
없으면 새 트랜잭션을 생성한다.
부분 롤백 지원이 필요한 경우

트랜잭션 전파 오류: Self Invocation

하나의 클래스 내에서 @Transactional이 붙은 다른 메서드를 호출하면, 프록시를 거치지 않고 실제 객체의 내부 메서드를 직접 호출하기 때문에 트랜잭션 전파 설정이 적용되지 않는다.

@Service
public class UserService {

    @Transactional  // (propagation = Propagation.REQUIRED) Default
    public void outerMethod() {
        // DB 작업 1
        innerMethod(); // 내부 호출 (프록시 X)
    }

    @Transactional (propagation = Propagation.REQUIRES_NEW)
    public void innerMethod() {
        // DB 작업 2
    }
}

outerMethod()가 Transaction A를 시작하고, innerMethod()는 프록시를 거치지 않아 REQUIRES_NEW가 적용되지 않아 Transaction A 안에서 그대로 실행된다. 결국, 두 작업이 하나의 트랜잭션에서 Commit/Rollback된다.
이를 해결하기 위해서, 트랜잭션 단위가 다른 메서드는 별도의 클래스로 분리하여 주입받아 사용한다.

@Service
public class OuterService {
    @Autowired
    private InnerService innerService;

    @Transactional  // (propagation = Propagation.REQUIRED)
    public void outerMethod() {
        // DB 작업 1
        innerService.innerMethod();
        // DB 작업 3
    }
}
@Service
public class InnerService {
    @Transactional  // (propagation = Propagation.REQUIRED)
    public void innerMethod() {
        // DB 작업 2
        // 만약 여기서 예외가 발생하여 롤백 되면,
        // outerMethod의 'DB 작업 1'까지 모두 롤백 된다.
    }
}

트랜잭션 예외와 Rollback 규칙

@Transactional은 기본적으로 RuntimeException (Unchecked Exception)과 Error가 발생했을 때만 Rollback한다. 따라서 Checked Exception이 발생하면 기본 정책 상 Rollback이 아니라 Commit이 된다. 또한try-catch로 해당 예외를 삼켜도 정상 리턴으로 간주되므로, Commit되어 데이터 정합성 문제가 발생할 수 있다. 따라서 모든 예외에 대해 Rollback하고 싶다면 rollbackFor 속성을 사용한다.

트랜잭션 동시성 제어 (Consistency Control)

여러 사용자가 동시에 데이터에 접근하고 수정할 때 발생할 수 있는 데이터 불일치 문제를 방지하기 위한 제어로, JPA는 낙관적 Lock과 비관적 Lock을 제공한다.

낙관적 Lock

트랜잭션 충돌이 자주 발생하지 않을 것이라고 가정하는 방식이다. DB 레코드 자체에 lock을 걸지 않고, @Version을 사용하여 데이터의 변경 여부를 확인한다.

@Entity
public class Article {
    @Id
    @GeneratedValue
    private Long id;
    
    private String content;
    
    @Version
    private Long version;
}

엔터티에 @Version 어노테이션이 붙은 필드 (주로 숫자 타입)을 추가하여, 데이터를 조회할 때 함께 가져온다. 데이터를 수정하고 트랜잭션을 커밋할 때, 현재 데이터베이스의 버전과 조회했던 시점의 버전을 비교한다. 버전이 일치하면 데이터를 수정하고 버전을 1 증가시키고, 일치하지 않으면 (다른 트랜잭션이 먼저 수정한 경우), ObjectOptimisticLockingFailureException을 발생시키고 수정을 실패 처리한다.

장단점

  • 장점: DB에 직접적인 lock을 걸지 않아 부하가 적고 성능이 좋다.
  • 단점: 충돌이 발생하면 개발자가 직접 예외를 처리하고 재시도 로직 등을 구현해야 한다.

비관적 Lock

트랜잭션 충돌이 자주 발생할 것이라고 가정한다. 데이터에 접근 시점부터 데이터베이스 레코드에 직접 lock을 걸고, 다른 트랜잭션은 해당 lock이 해제될 때까지 접근할 수 없다.

Lock 모드

  • PESSIMISTIC_WRITE: 데이터를 수정하기 위해 설정한다. 다른 트랜잭션은 해당 데이터를 읽거나 쓸 수 없다.
  • PESSIMISTIC_READ: 다른 트랜잭션이 해당 데이터를 수정하는 것을 방지한다. DB에 따라 읽기는 허용될 수 있다.

장단점

  • 장점: 데이터 정합성을 확실하게 보장하여, 충돌이 잦은 환경에서 데이터 무결성을 유지하는 데 효과적이다.
  • 단점: 데이터베이스에 직접 lock을 걸기 때문에 성능 저하가 발생할 수 있다. 다른 트랜잭션의 대기 시간이 길어져 전체적인 시스템 성능에 영향을 주고, 데드락이 발생할 수 있다.
profile
선형의 비선형적 기록 🐜

0개의 댓글