[Spring] 스프링 트랜잭션의 개념 및 적용 (@Transactional 사용법)

트랜잭션이란?

2023.10.10 - [Postgresql] - [PostgreSQL] 트랜잭션(Transaction)의 개념 및 사용

 

[PostgreSQL] 트랜잭션(Transaction)의 개념 및 사용

1. 트랜잭션(Transaction)이란? 트랜잭션은 데이터베이스에서 실행되는 일련의 작업들이다. 트랜잭션은 데이터베이스의 무결성 및 작업 간 충돌방지, 데이터 검증을 위해 필수적인 요소이다. 단순

junhkang.tistory.com

스프링에서 제공하는 트랜잭션

◆ 동기화 (Synchronization) 

트랜잭션을 시작하기 위한 Connection 객체를 특별 저장소에 보관하고 필요할 때 쓸 수 있도록 한다.

작업 쓰레드마다 Connection 객체를 독립적으로 관리하기에 멀티 스레드 환경에서도 충돌이 발생하지 않는다. 하지만 JDBC가 아닌 Hiberate 같은 기능을 사용한다면 JDBC 종속적인 트랜잭션 동기화 코드들은 문제가 발생한다. 대표적으로 Hibernate는 Connection이 아니라 Session 객체를 사용하기 때문이다. 이를 해결하기 위해 트랜잭션 관리 부분을 추상화한 기술을 제공하고 있다.

 

 추상화 (Abstraction)

어플리케이션에 각 기술(JDBC, Hibernate 등) 종속된 코드를 사용하지 않고 일관된 트랜잭션 처리를 할 수 있다.

(스프링의 트랜잭션 경계 설정을 위한 추상 인터페이스는 PlatformTransactionManager)

 

 트랜잭션 분리

트랜잭션과 비즈니스 로직 코드가 얽혀 있는 경우 깔끔한 분리를 위해 @Transactional 어노테이션을 지원한다.

@Transactional 어노테이션을 통한 트랜잭션 제어

해당 어노테이션이 붙으면 스프링은 트랜잭션 관리 대상으로 등록하고 설정된 트랜잭션 속성을 부여한다. 어노테이션을 사용하면 메서드 단위로 세밀하게 설정이 가능하며 직관적이어서 사용하는 것을 권장한다.

트랜잭션 전파 (Propagation)

트랜잭션 경계에서 이미 진행중이거나 이미 진행 중인 트랜잭션이 있거나 없을 때 어떻게 상호작용 할 것인가를 결정한다.

A작업에 대한 트랜잭션이 진행중이고 B작업이 이어서 시작될 때 각 옵션별로 다음과 같이 수행된다.

 

 PROPAGATION_REQUIRED (기존 트랜잭션에 참여)

  • 기본설정으로 모든 트랜잭션 매니저가 지원한다.
  • B 작업은 새로운 트랜잭션을 만들지 않고 A에 트랜잭션에 참여한다. 이미 시작된 트랜잭션이 없다면 새로 생성한다.
  • A,B가 같은 트랜잭션상에서 진행되기 때문에 A작업을 진행 중 B작업을 수행하고 다시 남은 A의 작업을 처리할 때 예외처리가 되면 A, B가 모두 롤백된다. 

 PROPAGATION_REQUIRES_NEW (신규 트랜잭션 생성)

  • 항상 새로운 트랜잭션을 시작한다.
  • B 작업을 위한 독립적인 트랜잭션을 생성한다. 이미 시작된 트랜잭션이 있으면 잠시 보류시키고 새로운 트랜잭션을 생성한다.
  • B 트랜잭션 경계를 나오는 순간 B 트랜잭션은 독자적으로 커밋 혹은 롤백되며 A작업에 영향을 주지 않는다.
  • A의 작업 간 예외가 발생되어 롤백되어도 B작업엔 영향을 주지 않는다.

 PROPAGATION_SUPPORTED (기존 트랜잭션 참여 혹은 트랜잭션 없이 동작)

  • B작업은 트랜잭션을 새로 만들지 않고 A트랜잭션에 참여한다. 이미 시작된 트랜잭션이 없을 경우 트랜잭션 없이 진행된다.

 PROPAGATION_NOT_SUPPORTED (트랜잭션 없이 동작)

  • B작업에 대한 트랜잭션이 없이 진행된다.
  • 이미 시작된 트랜잭션이 있는경우 보류시키고, 트랜잭션을 사용하지 않은 채로 B작업을 수행한다.
  • B가 단순 조회라면 트랜잭션이 필요없을 수 있다.

 PROPAGATION_MANDATORY (선행 트랜잭션 필수)

  • B작업은 A작업의 트랜잭션에 참여한다. 이미 시작된 트랜잭션이 없을 경우 예외를 발생시킨다.
  • 독립적으로 트랜잭션이 진행되면 안되는 경우에 사용된다.

 PROPAGATION_NEVER (트랜잭션 미사용)

  • 이미 시작된 트랜잭션이 있을경우 예외를 발생시키고, 트랜잭션을 사용하지 않도록 한다.

 PROPAGATION_NESTED (자식 트랜잭션 생성)

  • 이미 시작된 트랜잭션이 있을 경우 중첩(자식) 트랜 잭션을 생성한다.
  • 먼저시작된 부모 트랜잭션의 커밋, 롤백은 자식 트랜잭션에 영향을 주지만,
  • 자식 트랜잭션의 커밋,롤백은 부모 트랜잭션에 영향을 주지 않는다.

예를 들어 중요작업의 마무리 단계에 로그를 DB에 저장할 경우, 로그 저장에 대한 에러는 중요작업을 롤백하지 않고, 중요 작업 자체에 예외가 발생하면 로그는 저장되지 않아야 할 때 사용할 수 있다. JDBC3.0의 SavePoint를 지원하는 드라이버를 사용할 경우 사용가능하다. 사용가능한 트랜잭션 매니저를 확인 후 사용해야 한다.

격리 수준 (Isolation)

모든 DB 트랜잭션은 격리 수준을 가지고 있다. 서버에서는 여러 개의 트랜잭션이 동시에 진행될 수 있고, 모든 트랜잭션을 독립적으로 격리시킨 후 순차적으로 처리하면 안전하긴 하지만 성능이 크게 떨어질 수밖에 없다. 그러므로 적절한 격리 수준을 통해 가능한 많은 트랜잭션을 동시에 실행시키며 제어해야한다. JDBC 드라이버나 DataSource 등을 통해 설정변경 가능하며, 트랜잭션 단위로 격리도 가능하다.

DefaultTransactionDefinition의 격리수준 설정은 ISOLATION_DEFAULT로 DataSource에 정의된 격리 수준을 따른다.

 DEFAULT

  • 드라이버 기본설정을 따른다. 대부분 READ_committed 기본 격리 수준을 가진다.

 READ_uncommitted (level 0)

  • 가장 낮은 격리 수준
  • 커밋되지 않은 데이터에 대한 읽기가 가능하다.
  • 하나의 트랜잭션이 커밋되기 전에 그 변화가 다른 트랜잭션에 그대로 노출되는 문제가 있다.
  • 가장 빠르기 때문에 데이터의 무결성이 조금 떨어지더라도 성능을 극대화할 때 의도적으로 사용한다.

 READ_committed (level 1)

  • 스프링 기본 속성이다
  • 트랜잭션이 커밋 확정된 데이터만 읽기가 가능하다.
  • 하나의 트랜잭션이 읽은 로우를 다른 트랜잭션이 수정할 수 있어서 처음 트랜잭션이 같은 로우를 다시 읽을 때 내용이 달라져 있을 수 있다.

 REAPEATABLE_READ (level 2)

  • 하나의 트랜잭션이 읽은 ROW를 다른 트랜잭션이 수정할 수 없도록 막아준다.
  • 새로운 ROW 추가는 막지 않는다
  • SELECT로 ROW를 다시 가져오는 경우 변경은 없지만 신규 추가된 데이터는 있을 수 있다.

 SERIALIZABLE (level 3)

  • 가장 강력한 트랜잭션 격리 수준
  • 트랜잭션을 순차적으로 진행시켜 준다.
  • 트랜잭션 완료 시까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 여러 트랜잭션이 동시에 테이블을 참조할 수 없다.
  • 가장 안전하지만 가장 성능이 떨어지기 때문에 극단적인 상황이 아니라면 사용하면 안 된다
  • 데이터의 일관성 및 동시성 제어를 위해 MVCC를 사용하지 않는다.

2023.10.06 - [Postgresql] - [PostgreSQL] MVCC (Multi-Version Concurrency Control)

 

[PostgreSQL] MVCC (Multi-Version Concurrency Control)

1. MVCC란? 동시성 제어를 위해 lock을 사용하는 대부분의 다른 데이터베이스 시스템과 달리 Postgres는 다중 버전 모델(multiversion model)을 사용하여 데이터 일관성을 유지한다. 각 트랜잭션이 데이터

junhkang.tistory.com

제한시간 (timeout)

  • 트랜잭션을 수행하는 제한시간을 설정할 수 있다.
  • INT 타입 초단위로 설정할 수 있다. 
  • timeoutString을 설정하면 문자열로 설정 가능
  • 기본값은 트랜잭션 시스템 제한시간
  • 트랜잭션 매니저가 이 기능을 지원하지 않는다면 예외 발생
  • 트랜잭션을 직접 실행하는 PROPAGATION_REQUIRED, PROPAGATION_REQUIRED_NEW에 사용해야지만 의미가 있다.

읽기 전용 (READ_ONLY)

성능최적화와 쓰기 방지의 목적으로 읽기 전용을 설정할 수 있다. 읽기 전용 설정은 트랜잭션 내에 데이터를 조작하는 시도를 막아주고, 데이터 엑세스 성능을 최적화한다.

일반적으로 읽기전용 트랜잭션이 시작된 후 INSERT, UPDATE, DELETE 등의 데이터 변경 작업이 시작되면 예외가 발생되지만,

일부 트랜잭션 매니저의 경우 읽기전용 속성을 무시하는 경우가 있기에 주의하여야 한다.

롤백/커밋 예외

체크 예외란 Exception 클래스 하위 클래스, 언체크 예외란 Exception 하위 RuntimeException의 하위 클래스이다. 

@Transactional 내에 예외발생 시 언체크 예외(RuntimeException) 면 자동으로 롤백하고 체크 예외가 발생되면 커밋한다.

스프링에서는 데이터 접근 관련 에러는 런타임 익셉션으로 전환하여 throw 하기 때문에 런타임 익셉션만 롤백 대상이기 때문이다.

롤백/커밋 동작방식의 변경을 원하면,

커밋 대상이지만 롤백을 발생시킬 예외나 클래스 이름은 각각 rollbackFor, rollbackForClassName으로 지정할 수 있고, 롤백 대상인 런타임 예외를 트랜잭션 커밋대상으로 지정하기 위해서는 noRollbackFor 또는 noRollbackForClassName을 사용할 수 있다.

 

 

참고

https://www.ibm.com/docs/ko/wxs/8.6.1?topic=framework-managing-transactions-spring

https://mangkyu.tistory.com/170

https://goddaehee.tistory.com/167