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 옵션
- value (String)
- 사용할 Transaction Manager 를 지정하는 한정자 옵션입니다.
- transactionManager (String)
- value의 별칭입니다.
- label (Array of String)
- 트랜잭션에대한 설명을 추가할 수 있습니다. 이때 transactionManager에의해 평가될 수 있습니다.
- propagation (enum: Propagation)
- 전파 수준 설정 옵션입니다.
- REQUIRED (Defualt)
- 이미 진행중인 트랜잭션이 있다면 해당 트랜잭션 속성을 따르고, 진행중이 아니라면 새로운 트랜잭션을 생성합니다.
- 격리 수준이 다른 기존 트랜잭션에 참여할 때 격리 수준 선언을 거부하려면 transactionManager에서 validateExistingTransactions 를 true로 전환하는것을 고려할 수 있습니다.
- REQUIRES_NEW
- 항생 새로운 트랜잭션을 생성한다. 이미 진행중인 트랜잭션이 있다면 잠깐 보류하고 해당 트랜잭션 작업을 먼저 진행합니다.
- 항상 독립적인 물리적 트랜잭션을 사용하며 외부 범위에 대한 기존 트랜잭션에는 참여하지 않습니다.즉, 외부 트랜잭션이 내부 트랜잭션의 롤백 상태에 영향을 받지 않도록할 수 있습니다.
- 하지만 외부 트랜잭션에 연결된 리소스는 바인딩된 상태로 유지됩니다.
- 따라서 여러 스레드가 활성 외부 트랜잭션에서 내부 트랜잭션에 대한 새 연결을 획득하기 위해 대기하는 경우 connection pool이 소진되고 잠재적으로 교착 상태 (deadlock) 가 될 수 있으며 connection pool은 이러한 내부 연결을 더 이상 진행할 수 없습니다.
- SUPPORT
- 이미 진행 중인 트랜잭션이 있다면 해당 트랜잭션 속성을 따르고, 없다면 트랜잭션을 설정하지 않습니다.
- NOT_SUPPORT
- 이미 진행중인 트랜잭션이 있다면 보류하고, 트랜잭션 없이 작업을 수행합니다.
- MANDATORY
- 이미 진행중인 트랜잭션이 있어야만, 작업을 수행한다. 없다면 Exception을 발생시킵니다.
- NEVER
- 트랜잭션이 진행중이지 않을 때 작업을 수행한다. 트랜잭션이 있다면 Exception을 발생시킵니다.
- NESTED
- 진행중인 트랜잭션이 있다면 중첩된 트랜잭션이 실행되며, 존재하지 않으면 REQUIRED와 동일하게 실행됩니다.
- REQUIRED (Defualt)
- 전파 수준 설정 옵션입니다.
- 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이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능하다.
- 레벨이 높을 수록 데이터 무결성을 유지할 수 있지만 비용이 많이 듦 → 적절하게 적용하는것이 중요
- timeout (int)
- 타임 아웃 옵션입니다.( REQUIRED 또는 REQUIRED_NEW의 propagation값에만 적용됩니다. )
- 기본값= -1 , no timeout
- timeoutString (String)
- timeout을 String으로 반환합니다.
- readOnly (boolean)
- 읽기- 쓰기트랜잭션 혹은 읽기 전용트랜잭션일지 결정합니다 ( REQUIRED 또는 REQUIRED_NEW 값에만 적용됩니다. )
- rollbackFor ( Array of Class objects , Throwable에서 파생되어야합니다. )
- 특정예외 발생 시 롤백합니다.
- rollbackForClassName ( Array of exception name patterns. )
- 롤백을 발생시켜야 하는 예외 이름 패턴을 지정하고 발생 시 롤백합니다.
- noRollbackFor ( Array of Class objects , Throwable에서 파생되어야합니다. )
- 특정예외 발생 시 롤백하지 않습니다.
- noRollbackForClassName ( Array of exception name patterns. )
- 롤백을 발생시켜야 하는 예외 이름 패턴을 지정하고 발생 시 롤백하지 않습니다.
6. @Transactional 사용 시 주의 사항.
@Transactional은 기본적으로 Unchecked Exception(RuntimeException), Error만 롤백합니다. 즉, CheckedException은 롤백하지 않습니다.
- 이때 하위 트랜잭션에서 RuntimeException을 던지면 rollback마크가 붙어 전역 트랜잭션에서 롤백됩니다.
- globalRollbackOnParticipationFailure속성을 true로 주면 최초 트랜잭션이 결정할 수 있게되지만 모든 자원이 데이터 접근 없이도 커밋에 지장이 없다는게 보장되어야합니다.
- 서브 트랜잭션에서 실패 처리할때는 "중첩 트랜잭션"을 사용하는것이 권장됩니다
- 전역 트랜잭션이 서브 트랜잭션의 시작시 잡아둔 savePoint까지 롤백됩니다.
- PROPAGATION_NESTED가 해당 기능을 지원하는데, 중첩 트랜잭션이 지원되는 경우에만 동작하고, DataSourceTransactionManager에서만 동작한다고 합니다
레퍼런스
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
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
'Java > SpringBoot' 카테고리의 다른 글
[SpringBoot] SSE알림과 비동기 (0) | 2023.12.16 |
---|---|
ProcessBuilder를 통해 Python파일 실행하기 (0) | 2023.09.28 |
스프링 부트 환경변수 설정하기 (0) | 2023.09.19 |