@Transactional 어노테이션을 이용한 트랜잭션 관리
# Back-End/Spring

@Transactional 어노테이션을 이용한 트랜잭션 관리

 

주제

  • @Transactional 어노테이션에 대해 알아본다.
  • @Transactional을 적용해본다.

 


1. @Transactional 어노테이션이란?

1.1 트랜잭션이란?

트랜잭션이란 어떠한 작업들의 묶음이다.

 

트랜잭션이란 어떠한 작업들의 묶음이다.

 

데이터를 동기화 시키기 위한 작업들을 나열해보면 다음과 같다.

  1. 새로운 데이터를 받아옴
  2. 필요에 따라 데이터를 백업
  3. 기존 데이터에 반영(삭제, 수정, 저장)

이러한 여러 작업을 하나로 묶는다면 동기화 트랜잭션으로 만들 수 있다.

 

트랜잭션은 ACID 라는 특성이 있는데, 이는 하나의 트랜잭션에서 일어나는 작업들의 일관성/안전성을 보장하기 위한 특성이다. Spring 에서는 @Transactional 어노테이션을 통해 트랜잭션을 적절하게 관리할 수 있다.

 

1.2 @Transactional 어노테이션

1.2.1 정의

@Transactional은 클래스나 메서드에 붙일 수 있고, 적용한 범위 내 작업들을 트랜잭션으로 묶을 수 있도록 하는 어노테이션이다.
직접 객체를 만들 필요 없이 선언만으로도 트랜잭션 관리를 가능하게 하여, 선언적 트랜잭션이라고도 한다.

 

1.2.2 @Transactional 주의 사항

1) Rollback작동 여부

기본적으로 Rollback은 Runtime/Unchecked Exception에 대해서만 작동한다.

Checked Exception은 트랜잭션의 Rollback을 발생시키지 않기 때문에 임의로 적용시켜줘야 한다.

(실행 도중 발생할 수 있는 오류, 개발자가 임의로 정의한 Exception 들이 Runtime Exception)

 

 

 

2) 프록시에 의한 트랜잭션 기능 실행

 

 @Transactional은 Proxy 기반이며 AOP로 구성되어 있다. 

 

Spring은 @Transactional을 적용한 곳(클래스/메소드)에 프록시를 생성하고, 이 프록시 객체를 통해 클래스/메소드가 실행

됐을 경우에만 트랜잭션이 적용된다. (프록시를 통해 들어오는 외부 메소드 호출만이 가로채질 수 있음)

 

때문에 같은 클래스에서 트랜잭션이 정의되어 있지 않은 메소드에서 트랜잭션이 정의된 메소드를 실행했을 경우 트랜잭션은 시작되지 않는다. (프록시 객체를 통해 실행한 것이 아니기 때문에)

 

이 경우는 트랜잭션이 정의된 메소드에서 다른 트랜잭션이 정의된 메소드를 호출하도록 해주거나, 각각 다른 클래스로 분리해주면 해결된다. 트랜잭션이 정의된 곳에서 다른 트랜잭션이 정의된 곳을 호출했을 경우 상위 트랜잭션의 속성이 전이된다.

 

 

3) 접근 제어자

@Transactional은 public 접근 제어자에만 적용 가능하다.

 

 

1.2.3 @Transactional의 다양한 속성(Attribute)

1) 주요 속성

  • 트랜잭션의 전파 유형(propagation)
  • 트랜잭션의 격리 수준(isolation)
  • 트랜잭션에 의해 래핑된 작업에 대한 시간 초과(timeout)
  • 읽기 전용 트랜잭션 여부(readOnly)
  • 트랜잭션에 대한 롤백 규칙(rollbackFor)

 

2) 전체 속성

package org.springframework.transaction.annotation;

...

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    // 기본적으로 PlatformTransactionManager를 통해 트랜잭션이 관리된다.
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};

    // 트랜잭션 전이
    Propagation propagation() default Propagation.REQUIRED;

    // 트랜잭션 격리 수준
    Isolation isolation() default Isolation.DEFAULT;

    // 트랜잭션 지속될 수 있는 시간(timeout)
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

    String timeoutString() default "";

    // 읽기 전용 트랜잭션 사용 여부
    boolean readOnly() default false;

    // 롤백이 수행되어야 하는 예외 클래스들
    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    // 롤백이 수행되지 않아야 하는 예외 클래스들
    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};

}

 

트랜잭션 격리 수준(isolation)

  • DEFAULT : 기본 격리 수준(db의 격리 수준을 따름)
  • READ UNCOMMITED : commit 되지 않은 데이터를 읽을 수 있게 허용해준다. Dirty Read, Non-repeatable read, phantom read 발생 가능
  • READ COMMITED : commit 된 데이터만 읽을 수 있게 허용해준다. Non-repeatable read, phantom read 발생 가능
  • REPEATABLE READ : 반복 가능한 읽기. 트랜잭션이 시작되기 전에 commit 된 내용에 대해서만 조회할 수 있는 격리 수준이다. 트랜잭션이 완료될 때까지 select 쿼리가 접근하는 모든 데이터에 shared lock이 걸린다.(수정할 수 없게 됨). phantom read 발생 가능
  • SERIALIZABLE : 직렬화 가능. 가장 강한 격리수준. 성능은 좋지 못하다. 트랜잭션이 완료될 때까지 select 쿼리가 접근하는 모든 데이터에 shared lock이 걸린다.

 

트랜잭션 전이(propagation)

  • REQUIRED : 부모 트랜잭션을 이어간다. 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성한다.
  • REQUIRES_NEW : 부모 트랜잭션이 있던 말던, 무조건 새로운 트랜잭션이 생성된다.
  • SUPPORT : 부모 트랜잭션을 이어간다. 부모 트랜잭션이 없을 경우 non-transactional 하게 실행한다.
  • MANDATORY : 부모 트랜잭션을 이어간다. 부모 트랜잭션이 없을 경우 예외를 발생시킨다.
  • NOT_SUPPORT : 부모 트랜잭션이 있던 말던, non-transactional로 실행한다. 부모 트랜잭션이 있다면 부모 트랜잭션을 중지시킨다.
  • NEVER : 항상 non-transactional하게 실행된다. 부모 트랜잭션이 존재한다면 예외를 발생시킨다.
  • NESTED : 부모 트랜잭션에서 진행될 경우 별개로 commit되거나 roll back될 수 있다. 부모 트랜잭션이 없을 경우 REQUIRED처럼 동작한다.

 

트랜잭션 롤백

  • rollbackFor = Exception.class 를 통해 UncheckedException이 발생했을 경우에도 롤백시킬 수 있다.
  • 롤백 시키는 방법은 선언적 방법과 프로그래밍적 방법이 있으나 선언적인 방법을 선호해야 한다.

 

2. @Transactional 어노테이션 적용해보기

1.1 읽기 전용 트랜잭션

@Transactional(readOnly = true)
public SomeVO findSomethingById(Long id) {
	...
}

 

 

1.2 UncheckedException에 대한 롤백 처리

@Transactional(rollbackFor = Exception.class)
public SomeVO saveSometing(Request request) {
	...
}

 

1.3 같은 클래스 안에서 타 트랜잭션 호출해보기

// TestService Class

@Transactional(rollbackFor = Exception.class)
public void saveSometing(Request request) {	
    // A Service 호출
	aService.save();
    
    // B Service 호출
	bService.save();
    
    ...
}

 

TestService 서비스의 saveSomething 메소드를 호출했을 때 트랜잭션이 시작되며,

이 트랜잭션은 aService, bService에 전이된다(물려받는다)

 

aService, bService를 하나의 트랜잭션으로 묶었기 때문에 둘 중 하나라도 오류가 나면 같이 롤백된다.

 

 

참고자료

https://www.baeldung.com/transaction-configuration-with-jpa-and-spring

https://velog.io/@max9106/Transaction

728x90