[SpringBoot] @Transactional을 알아보자

0.시작하며

Spring Boot 프레임워크를 사용하며 개발할때 자주 사용하게되는 어노테이션인 @Transactional에 대하여 알아보며 생각보다 많은 기능이 있어서 놀랐고 이를 정리해보겠습니다.

 

1. 트랜잭션이란?

데이터베이스의 상태를 변경시키기위해 수행하는 작업의 단위입니다.

이때 작업은 select, update, insert, delete등의 쿼리가 될 수 있습니다.

 

2. 트랜잭션의 성질, ACID

  • Atomicity, 원자성
    • 트랜잭션 내의 모든 연산이 완전히 반영되거나 반영되지 않아야합니다.
    • 즉, 하나라도 오류가 발생한다면 모든 변경사항이 취소되어야합니다.
    • rollback할때 savepoint를 지정하여 같은rollback할 수 있습니다.
      • 단, rollback한 savepoint 이후로 설정되어있던 다른 savepoint를 참조하거나 롤백할 수 없습니다.
  • Consistency, 일관성
    • 트랜잭션이 완료된 후에도 DB는 일관된 상태를 유지해야합니다.
    • 트랜잭션은 항상 일관된 규칙을 준수하고 데이터의 무결성을 보장합니다.
    • 트랜잭션 실행 전, 후 DB의 모든 제약조건이 만족되어야합니다.
  • Isolation, 독립성
    • 동시에 여러 트랜잭션이 수행될 때 각 트랜잭션이 서로 영향을 주지 않도록 보장해야 합니다.
    • 격리 수준에 따라 다른 트랜잭션의 중간 결과를 보거나 영향을 받지 않도록 할 수 있습니다.
    • Locking, 타임스탬프기반 제어, 낙관적 동시성 제어, 다중 버전 동시성 제어 등..
  • Durability, 지속성
    • 트랜잭션이 성공적으로 완료된 후에는, 시스템 장애가 생기더라도 그 결과가 영구적으로 저장되어야 합니다.

3. 트랜잭션의 기능

  • commit
    • 트랜잭션의 모든 작업이 정상적으로 성공하는 경우 DB에 영구적으로 반영하는 것
  • rollback
    • 트랜잭션 중 오류가 발생하거나 사용자가 취소를 원할때 모든 변경사항을 취소합니다.
    • DB의 일관성을 유지할 수 있습니다

4. @Transactional 이란?

Spring 프레임워크에서 트랜잭션을 선언적으로 관리하기위한 핵심요소이다. 즉, Java 소스코드로 transaction동작을 정의하기위한 어노테이션.

 

 

5. @Transactional의 사용

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

	public Foo getFoo(String fooName) {
		// ...
	}

	// these settings have precedence for this method
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	public void updateFoo(Foo foo) {
		// ...
	}
}

 

위와 같이 클래스 레벨에서 사용되는 주석은 선언 클래스의 모든 메소드(+ 해당 서브클래스)에 대한 기본값을 나타냅니다.

메서드에 직접 사용한 어노테이션은 우선순위가 높습니다. 메서드에 적용된 @Transactional은 더 많은 옵셥이 적용되어 있는데요 해당 옵션의 의미를 알아보겠습니다.

 

@Transactional 옵션

  1. value (String)
    • 사용할 Transaction Manager 를 지정하는 한정자 옵션입니다.
  2. transactionManager (String)
    • value의 별칭입니다.
  3. label (Array of String)
    • 트랜잭션에대한 설명을 추가할 수 있습니다. 이때 transactionManager에의해 평가될 수 있습니다.
  4. propagation (enum: Propagation)
    • 전파 수준 설정 옵션입니다. 
      • REQUIRED (Defualt)
        • 이미 진행중인 트랜잭션이 있다면 해당 트랜잭션 속성을 따르고, 진행중이 아니라면 새로운 트랜잭션을 생성합니다.
        • 격리 수준이 다른 기존 트랜잭션에 참여할 때 격리 수준 선언을 거부하려면 transactionManager에서 validateExistingTransactions 를 true로 전환하는것을 고려할 수 있습니다.
      • REQUIRES_NEW
        • 항생 새로운 트랜잭션을 생성한다. 이미 진행중인 트랜잭션이 있다면 잠깐 보류하고 해당 트랜잭션 작업을 먼저 진행합니다.
        • 항상 독립적인 물리적 트랜잭션을 사용하며 외부 범위에 대한 기존 트랜잭션에는 참여하지 않습니다.즉, 외부 트랜잭션이 내부 트랜잭션의 롤백 상태에 영향을 받지 않도록할 수 있습니다.
        • 하지만 외부 트랜잭션에 연결된 리소스는 바인딩된 상태로 유지됩니다.
        • 따라서 여러 스레드가 활성 외부 트랜잭션에서 내부 트랜잭션에 대한 새 연결을 획득하기 위해 대기하는 경우 connection pool이 소진되고 잠재적으로 교착 상태 (deadlock) 가 될 수 있으며 connection pool은 이러한 내부 연결을 더 이상 진행할 수 없습니다.
      • SUPPORT
        • 이미 진행 중인 트랜잭션이 있다면 해당 트랜잭션 속성을 따르고, 없다면 트랜잭션을 설정하지 않습니다.
      • NOT_SUPPORT
        • 이미 진행중인 트랜잭션이 있다면 보류하고, 트랜잭션 없이 작업을 수행합니다.
      • MANDATORY
        • 이미 진행중인 트랜잭션이 있어야만, 작업을 수행한다. 없다면 Exception을 발생시킵니다.
      • NEVER
        • 트랜잭션이 진행중이지 않을 때 작업을 수행한다. 트랜잭션이 있다면 Exception을 발생시킵니다.
      • NESTED
        • 진행중인 트랜잭션이 있다면 중첩된 트랜잭션이 실행되며, 존재하지 않으면 REQUIRED와 동일하게 실행됩니다.
  5. isolation (enum: Isolation)
    • 격리 수준 옵션 ( REQUIRED 또는 REQUIRED_NEW의 propagation값에만 적용됩니다.)
    • 격리 수준에 따라 나타날 수 있는 부작용
      • Dirty read: 동시에 수행되는 트랜잭션간 커밋되지 않은 변경 사항 읽기
      • Nonrepeatable read: 동시에 수행되는 두 트랜잭션중 A가 읽은 행을 B가 업데이트하고 커밋하는 경우 해당 행을 다시 읽을 때 다른 값을 가져옵니다
      • Phantom read: 다른 트랜잭션이 범위의 일부 행을 추가 또는 제거하고 커밋하는 경우 범위 쿼리를 다시 실행한 후 다른 행(다른 결과)을 얻습니다
    • 격리 레벨 
      • default : DB isolation 을 따릅니다.
      • READ_UNCOMMITED : 가장 낮은 격리 수준이며 가장 많은 동시 액세스를 허용합니다.
        •  위 세 가지 동시성 부작용이 모두 일어납니다
      • READ_COMMITED : Dirty read를 방지합니다.
        • 한 트랜잭션이 아직 커밋되지 않은 변경사항을 가지고 있다면, 이 변경사항은 다른 트랜잭션에 영향을 미치지 않습니다. 즉, 다른 트랜잭션은 이 미커밋 변경사항을 "보지" 못합니다.
        • 따라서, 한 트랜잭션이 데이터를 커밋한 후에 다른 트랜잭션이 같은 데이터를 조회하면, 커밋된 새로운 데이터를 보게 됩니다.
      • REPEATEABLE_READ : Dirty read와 Nonrepeatable read를 방지합니다.
        • 두 부작용을 방지하므로 동시수행 트랜잭션간 커밋되지 않은 변경에 영향을 받지 않습니다  또한 행을 다시 조회할 때는 다른 결과가 나오지 않습니다.
        • 그러나 범위 쿼리를 다시 실행할 때는 행을 새로 추가하거나 제거할 수 있습니다.
        • lost update를 방지하기 위한 최저 수준레벨입니다. lost update는 둘 이상의 동시수행 트랜잭션이 동일한 행을 읽고 업데이트할 때 발생합니다.
        • 해당 레벨은 하나의 행에 동시 엑세스를 전혀 허용하지 않기때문에 유실된 업데이트는 발생하지 않습니다.
      • SERIALIZABLE : 가장 높은 격리수준으로 모든 부작용을 방지합니다. 하지만 성능 저하의 우려가 있음
        • 트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능하다.
      • 레벨이 높을 수록 데이터 무결성을 유지할 수 있지만 비용이 많이 듦 → 적절하게 적용하는것이 중요
  6. timeout (int)
    • 타임 아웃 옵션입니다.( REQUIRED 또는 REQUIRED_NEW의 propagation값에만 적용됩니다. )
    • 기본값= -1 , no timeout
  7. timeoutString (String)
    • timeout을 String으로 반환합니다.
  8. readOnly (boolean)
    • 읽기- 쓰기트랜잭션 혹은 읽기 전용트랜잭션일지 결정합니다 ( REQUIRED 또는 REQUIRED_NEW 값에만 적용됩니다. )
  9. rollbackFor ( Array of Class objects , Throwable에서 파생되어야합니다. )
    • 특정예외 발생 시 롤백합니다.
  10. rollbackForClassName ( Array of exception name patterns. )
    • 롤백을 발생시켜야 하는 예외 이름 패턴을 지정하고 발생 시 롤백합니다.
  11. noRollbackFor ( Array of Class objects , Throwable에서 파생되어야합니다. )
    • 특정예외 발생 시 롤백하지 않습니다.
  12. noRollbackForClassName ( Array of exception name patterns. )
    • 롤백을 발생시켜야 하는 예외 이름 패턴을 지정하고 발생 시 롤백하지 않습니다.

 

 

6. @Transactional 사용 시 주의 사항.

@Transactional은 기본적으로 Unchecked Exception(RuntimeException), Error만 롤백합니다. 즉, CheckedException은 롤백하지 않습니다.

  • 이때 하위 트랜잭션에서 RuntimeException을 던지면 rollback마크가 붙어 전역 트랜잭션에서 롤백됩니다.
  • globalRollbackOnParticipationFailure속성을 true로 주면 최초 트랜잭션이 결정할 수 있게되지만 모든 자원이 데이터 접근 없이도 커밋에 지장이 없다는게 보장되어야합니다.
  • 서브 트랜잭션에서 실패 처리할때는 "중첩 트랜잭션"을 사용하는것이 권장됩니다
    • 전역 트랜잭션이 서브 트랜잭션의 시작시 잡아둔 savePoint까지 롤백됩니다.
    • PROPAGATION_NESTED가 해당 기능을 지원하는데, 중첩 트랜잭션이 지원되는 경우에만 동작하고, DataSourceTransactionManager에서만 동작한다고 합니다

 

레퍼런스

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html#label()

 

Transactional (Spring Framework 6.1.3 API)

Describes a transaction attribute on an individual method or on a class. When this annotation is declared at the class level, it applies as a default to all methods of the declaring class and its subclasses. Note that it does not apply to ancestor classes

docs.spring.io

https://techblog.woowahan.com/2606/

 

응? 이게 왜 롤백되는거지? | 우아한형제들 기술블로그

{{item.name}} 이 글은 얼마 전 에러로그 하나에 대한 호기심과 의문으로 시작해서 스프링의 트랜잭션 내에서 예외가 어떻게 처리되는지를 이해하기 위해 삽질을 해본 경험을 토대로 쓰여졌습니다.

techblog.woowahan.com

https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html

 

Using @Transactional :: Spring Framework

The @Transactional annotation is metadata that specifies that an interface, class, or method must have transactional semantics (for example, "start a brand new read-only transaction when this method is invoked, suspending any existing transaction"). The de

docs.spring.io

https://www.baeldung.com/spring-transactional-propagation-isolation