본문 바로가기
Java & Kotlin/Spring

Spring @Transaction

by kiwi_wiki 2025. 1. 14.

@Transaction

스프링에서 제공하는 선언적 트랜잭션 관리를 위한 어노테이션

 

동작 구조

스프링에서 사용하는 프록시 구현체는 JDK Proxy(Dynamic Proxy), CGLib가 있다

두 방식의 가장 큰 차이점은 타겟의 어떤 부분을 상속받아서 프록시를 구현하느냐에 있다

  • JDK Proxy
    • 타겟의 상위 인터페이스를 상속받아 프록시 생성
    • 인터페이스를 구현한 클래스가 아니면 의존할 수 없다
    • 타겟에서 다른 구체 클래스에 의존하고 있다면 JDK 방식에서는 그 클래스(빈)를 찾을 수 없어 런타임 에러가 발생
    • JDK Proxy는 내부적으로 Reflection을 사용해서 추가적인 비용이 발생한다
  • CGLib Proxy
    • 타켓 클래스를 상속받아 프록시 생성
    • 인터페이스를 구현하지 않아도 된다
    • 구체 클래스에 의존하기 때문에 런타임 에러가 발생할 확률도 상대적으로 적다
    • Reflection 비용이 들지 않는다
    • Spring Boot에서는 CGLib을 사용한 방식을 기본으로 채택하고 있다

Proxy 형태로 동작하는 @Transactional

트랜잭션 처리를 위한 @Transational 애노테이션은 Spring AOP의 대표적인 예이다

  • 타겟에 대한 호출이 들어오면 AOP Proxy가 가로채서(intercept) 가져온다
  • AOP Proxy에서 Transaction Advisor가 commit 또는 rollback 등의 트랜잭션 처리를 한다
  • 트랜잭션 처리 외에 다른 부가 기능이 있을 경우 해당 Custom Advisor에서 그 처리를 한다
  • 각 Advisor에서 부가 기능 처리를 마치면 Target Method를 수행한다
  • interceptor chain을 따라 caller에게 결과를 전달한다
public class TransactionProxy {
    private final TransactonManager manager = TransactionManager.getInstance();
		...

    public void transactionLogic() {
        try {
            // 트랜잭션 전처리(트랜잭션 시작, autoCommit(false) 등)
            manager.begin();

            // 다음 처리 로직(타겟 비스니스 로직, 다른 부가 기능 처리 등)
            target.logic();

            // 트랜잭션 후처리(트랜잭션 커밋 등)
            manager.commit();
        } catch (Exception e) {
            // 트랜잭션 오류 발생 시 롤백
            manager.rollback();
        }
    }
}

속성

  • propagation: 트랜잭션 전파 방식 설정
    • REQUIRED: 기본 값. 미리 시작된 트랜잭션이 있으면 참여하고 없으면 새로 생성
    • SUPPORTS: 이미 시작된 트랜잭션이 있으면 참여하고 없으면 트랜잭션 없이 진행. 트랜잭션 경계 안에서 Connection 객체나 하이버네이트의 Session 등은 공유할 수 있음
    • MANDATORY: 이미 시작된 트랜잭션이 있으면 참여하고 없으면 예외 발생. 혼자서 독립적으로 트랜잭션을 진행하면 안 되는 경우에 사용
    • REQUIRES_NEW: 항상 새로운 트랜잭션 생성. 이미 진행중인 트랜잭션이 있으면 보류시키고 새로운 트랜잭션을 만들어 시작함
    • NOT_SUPPORTED: 이미 진행중인 트랜잭션이 있으면 이를 보류시키고 트랜잭션을 사용하지 않도록 함
    • NEVER: 이미 진행중인 트랜잭션이 있으면 예외를 발생시키며 트랜잭션을 사용하지 않도록 강제함
    • NESTED: 이미 진행중인 트랜잭션이 있으면 중첩(자식) 트랜잭션을 시작함. 먼저 시작된 부모 트랜잭션의 커밋과 롤백에는 영향을 받지만 자신의 커밋과 롤백은 부모 트랜잭션에 영향을 주지 않음. NESTED는 JDBC의 savepoint 기능을 사용하는데, DB 드라이버가 이를 지원하는지 확인이 필요하며 JPA에서 사용이 불가능하다.
  • isolation: 트랜잭션 격리 수준 설정. 동시에 여러 트랜잭션이 진행될 때 작업 결과를 다른 트랜잭션에게 어떻게 노출할 것인지 결정
    • DEFAULT
      • 사용하는 데이터 액세스 기술 또는 DB 드라이버의 디폴트 설정을 따름
      • 대부분의 DB는 READ_COMMITTED를 기본 격리 수준으로 가짐
    • READ_UNCOMMITTED: 가장 낮은 격리수준
      • 하나의 트랜잭션이 커밋되기 전에 그 변화가 다른 트랜잭션에 그대로 노출됨.
      • 하지만 가장 빠르기 때문에 데이터 일관성이 조금 떨어지더라도 성능을 극대화할 때 의도적으로 사용함
    • READ_COMMITTED: Spring의 기본 속성은 DEFAULT이며, DB는 일반적으로 READ_COMMITTED가 기본 속성임.
      • 다른 트랜잭션이 커밋하지 않은 정보는 읽을 수 없음
      • 대신 하나의 트랜잭션이 읽은 로우를 다른 트랜잭션이 수정할 수 있어서 처음 트랜잭션이 같은 로우를 다시 읽을 때 다른 내용이 발견될 수 있음
      • 실제 테이블의 값을 가져오는 것이 아니라 Undo 영역에 백업된 레코드에서 값을 가져옴
    • REPEATABLE_READ:
      • 하나의 트랜잭션이 읽은 로우를 다른 트랜잭션이 수정할 수 없도록 막아주지만 새로운 로우를 추가하는 것은 막지 않음
      • 따라서 SELECT로 조건에 맞는 로우를 전부 가져오는 경우 트랜잭션이 끝나기 전에 추가된 로우가 발견될 수 있음
      • MySQL에서는 트랜잭션마다 트랜잭션 ID를 부여하여 트랜잭션 ID보다 작은 트랜잭션 번호에서 변경한 것만 읽게 됨
      • Undo 공간에 백업해 두고 실제 레코드 값을 변경함
        • 백업된 데이터는 불필요하다고 판단하는 시점에 주기적으로 삭제
        • Undo에 백업된 레코드가 많아지면 MySQL 서버의 처리 성능이 떨어질 수 있음
      • 이런 한 변경 방식은 MVCC(Multi Version Concurrency Control)라고 부름
    • SERIALIZABLE: 가장 강력한 트랜잭션 격리 수준.
      • 트랜잭션을 순차적으로 진행시켜 줌.
      • 그러므로 여러 트랜잭션이 동시에 같은 테이블의 정보를 액세스 할 수 없음.
      • 가장 안전하지만 가장 성능이 떨어지므로 극단적으로 안전한 작업이 필요한 경우가 아니라면 사용해서는 안됨.

  • timeout: 트랜잭션 제한 시간 설정. 별도의 값을 설정해주지 않으면 트랜잭션 시스템의 제한시간을 따름
  • readOnly: 읽기 전용 트랜잭션 설정. 읽기 전용 트랜잭션이 시작된 이후 INSERT, UPDATE, DELETE의 작업이 진행되면 예외 발생
    • 사용하는 이유
      • 성능 최적화
        • 트랜잭션을 읽기 전용으로 설정하면 해당 메서드가 데이터를 읽기만 한다는 것을 DB에 알려줌으로써 쿼리 및 캐싱을 최적화할 수 있다
        • 읽기 전용으로 설정하면 데이터 변경이 일어나지 않기 때문에 변경 감지를 위한 스냅샷을 저장하는 동작을 하지 않아 성능이 향상되는 것을 기대할 수 있다
      • 데이터 일관성
        • 일반적으로 트랜잭션을 사용해서 DB에 데이터의 일관성과 무결성을 보장하기 위해 사용하는데 트랜잭션을 읽기 전용으로 설정하면 실수로 데이터르 수정해서 일관성을 위반할 가능성이 낮아진다
      • 가독성 향상
        • 코드를 작성하는 개발자는 @Transactional(readOnly= true) 이 설정된 메서드가 DB에서 데이터를 읽기만 한다는 것을 명확하게 확인할 수 있다. 이에 따라 코드의 가독성이 향상된다

주의 사항

  • private 메서드에는 적용 불가
  • 내부 메서드 호출(self-invocation) 시 동작하지 않음
  • 런타임 예외가 발생하면 롤백하고, 예외가 발생하지 않았거나 체크 예외가 발생하면 커밋한다
    롤백/커밋 동작 방색의 변경을 원하면 설정할 수 있다
    커밋 대상이지만 롤백을 발생시킬 예외나 클래스 이름은 각각 rollbackFor 또는 rollbackForClassName으로 지정할 수 있으며, 반대로 롤백 대상인 런타임 예외를 트랜잭션 커밋 대상으로 지정하기 위해서는 noRollbackFor 또는 noRollbackForClassName을 이용할 수 있다.
728x90
반응형

'Java & Kotlin > Spring' 카테고리의 다른 글

Spring Filter & Interceptor  (0) 2025.01.13
Spring JPA  (0) 2025.01.12
Spring Boot AutoConfiguration  (0) 2025.01.11
Spring Framework?  (0) 2025.01.10