Transaction 이란, 더 이상 나눌 수 없는 작업 단위(unit of work)을 말한다.
ACID
- Atomicity
transaction의 작업이 부분적으로 성공하는 일이 없도록 보장하는 성질이다. 송금하는 사람의 계좌에서 돈은 빠져나갔는데 받는 사람의 계좌에 돈이 들어오지 않는 일은 없어야 한다. - Consistency
transaction이 끝날 때 DB의 여러 제약 조건에 맞는 상태를 보장하는 성질이다. 송금하는 사람의 계좌 잔고가 0보다 작아지면 안 된다. - Isolation
transaction이 진행되는 중간 상태의 데이터를 다른 transaction이 볼 수 없도록 보장하는 성질이다. 송금하는 사람의 계좌에서 돈은 빠져나갔는데 받는 사람의 계좌에 돈이 아직 들어가지 않은 DB 상황을 다른 transaction이 읽으면 안 된다. - Durability
transaction이 성공했을 경우 해당 결과가 영구적으로 적용됨을 보장하는 성질이다. 한 번 송금이 성공하면 은행 시스템에 장애가 발생하더라도 송금이 성공한 상태로 복구할 수 있어야 한다.
DB는 그 자체로 완벽한 트랜잭션을 지원한다. 하지만 여러 개의 SQL이 사용되는 작업을 하나의 트랜잭션으로 취급해야 하는 경우, 여러 가지 작업이 하나의 트랜잭션이 되려면, 모든 SQL이 성공적으로 DB에서 수행되지 못하고 문제가 발생할 경우, 앞에서 처리한 SQL 작업도 취소시켜야 한다. 이런 취소 작업을 트랜잭션 롤백(transaction roolback)이라고 한다. 반대로 여러 개의 SQL을 하나의 트랜잭션으로 처리하는 경우에 모든 SQL 수행 작업이 다 성공적으로 마무리됐다고 DB에 알려줘서 작업을 확정시켜야 한다. 이것을 트랜잭션 커밋(transaction commit)이라고 한다 👉🏻 All or Nothing!
JDBC 트랜잭션
JDBC 트랜잭션의 시작과 종료는 Connection 을 통해 이루어지기 때문에 트랜잭션은 하나의 Connection을 가져와 사용하다가 닫는 사이에 일어난다. 트랜잭션이 한 번 시작되면 commit() 이나 rollback()이 호출되면 그에 따라 작업 결과가 DB에 반영되고, 트랜잭션은 종료된다.
일반적인 JDBC API를 사용할 때에는 Connection의 setAutoCommit(false)로 설정한 다음, 작업이 끝날 때, commit()을 호출하고, 예외가 발생하면, rollback()을 호출한다. 이렇게 하나의 DB 커넥션 안에서 만들어지는 트랜젝션을 로컬 트랜잭션이라 한다.
로컬 트랜잭션은 JDBC 연결과 관련된 트랜잭션과 같이 리소스별로 다르다. 로컬 트랜잭션은 사용하기 더 쉬울 수 있지만 하나 이상의 트랜잭션 리소스에서 작동할 수 없다는 단점이 있다. 예를 들어 JDBC 연결을 사용하여 트랜잭션을 관리하는 코드는 글로벌 JTA 트랜잭션 내에서 실행될 수 없다. 애플리케이션 서버는 트랜잭션 관리에 관여하지 않으므로 하나 이상의 리소스에 걸쳐 정확성을 보장하지 않는다. (하지만, 대부분의 애플리케이션은 단일 트랜잭션 리소스를 사용함.) 또 다른 단점은 로컬 트랜잭션은 프로그래밍 모델을 침해할 수 있다.
글로벌 트랜잭션을 통해 하나 이상의 트랜잭션 리소스( 일반적으로 관계형 데이터베이스 및 메시지 큐 )에서 처리할 수 있다. 애플리케이션 서버는 대게 사용하기에 느린 API인 JTA를 통해 글로벌 트랜잭션을 관리한다. 또한 JTA UserTransaction은 일반적으로 JNDI에서 제공되어야하며, 이는 JTA를 사용하기 위해 JNDI를 사용해야 한다는 것을 의미한다. 일반적으로 JTA는 애플리케이션 서버 환경에서만 사용할 수 있기 때문에 글로벌 트랜잭션은 응용 프로그램 코드의 재사용할 가능성이 있을 경우에만 사용한다.
Spring 트랜잭션
Spring Framework’s consistent programming model 은 글로벌 및 로컬 트랜잭션의 단점을 해결할 수 있다. 애플리케이션 개발자가 모든 환경에서 일관된 프로그래밍 모델을 사용할 수 있도록 한다. 코드를 한 번 작성하면 다른 환경에서 서로 다른 트랜잭션 관리 전략으로 이익을 얻을 수 있다. 스프링 프레임워크는 선언적 및 프로그램적 트랜잭션 관리를 모두 제공한다. 대부분의 사용자들은 선언적 트랜잭션 관리를 선호하고, 이것을 사용하기를 권장한다.
이러한 프로그램 트랜잭션 관리로 모든 인프라에 걸쳐 실행할 수 있는 Spring Framework 트랜잭션 인터페이스를 사용한다. 선호되는 선언적 모델을 사용하여, 개발자들은 일반적으로 트랜잭션 관리와 관련된 코드를 거의 작성하지 않는다, 따라서 스프링 프레임워크 트랜잭션 API 또는 다른 트랜잭션 API에 의존하지 않을 수 있다.
@Transactional
- Spring Framework의 선언적 트랜잭션 관리는 메소드/클래스 수준으로 지정할 수 있음
- 트랜잭션이 선언된 메소드/클래스 영역에서 처리 중 오류 발생시 모든 작업 사항 롤백
- 성공 시 커밋
- 스프링 aspect-oriented programming (AOP)을 이용
- Proxy 모드 (default) 에서는 외부 메서드에서 호출한 경우에만 인터셉트 되고, 동일한 객체의 다른 메소드를 호출하는 경우에는 런타임 시 트랜잭션이 발생하지 않음
☝🏻 Spring transaction 기본 사항- 외부에서 Invocation (호출) 한 method 에만 Proxy 기능 작동 (즉, 동일하지 않은 외부 Bean에서 호출하는 경우)
- public method 에만 Proxy 기능 작동
- Self-Invocation (Object 내에서 내부 method 호출 this.A()) 시 Proxy 기능 작동 안함.
이를 해결하기 위해 AspectJ를 사용하거나 사용하는 하나 이상의 메소드를 다른 클래스로 분리하는 방법이 있음,
@Transactional Settings
✔︎ 전파방식(propagation)
enum: Propagation
일반적으로, 트랜잭션 범위 내에서 실행되는 모든 코드는 해당 트랜잭션에서 실행된다. 그러나, 트랜잭션 컨텍스트가 이미 존재하는 경우, 특정 옵션을 통해 이 이벤트가 기존 트랜잭션에서 실행할 지 아니면 기존 트랜잭션은 잠시 중단하고, 새로운 트랜잭션에서 실행할 지 등을 지정할 수 있다.
(기본값: REQUIRED)
전파방식 |
설명 |
기존 트랜잭션이 있음 |
기존 트랜잭션이 없음 |
REQUIRED |
트랜잭션 상황에서 실행됨 |
참여 |
신규 |
REQUIRED_NEW |
자신만의 트랜잭션 상황에서 실행됨 |
신규 |
신규 |
SUPPORTS |
트랜잭션이 없더라도 실행 가능함 |
참여 |
X |
NOT_SUPPORTED |
트랜잭션이 없는 상황에서 실행 가능함 |
X |
X |
MANDATORY |
트랜잭션이 반드시 존재해야 함 |
참여 |
예외발생 |
NEVER |
트랜잭션이 반드시 없어야 함 |
예외 발생 |
X |
NESTED |
중첩된 트랜잭션에서 실행 기존 트랜잭션과는 독립적으로 커밋이나 롤백 가능 벤더 의존적이며 지원이 안되는 경우도 많음 |
중첩 |
REQUIRED와 동일 |
✔︎ 격리 수준(isolation level)
enum: Isolation
이 트랜잭션이 다른 트랜잭션의 작업과 분리되는 정도로, 다른 트랜잭션에서 커밋되지 않은 변경사항을 참조 수 있는 지 여부를 결정할 수 있다.
더티 읽기(dirty read)
- 한 트랜잭션에서 다른 트랜잭션에 의해 변경되었지만 아직 커밋되지 않은 데이터를 읽게 되는 문제
- 이 데이터가 커밋되지 않고 롤백되어 버린다면, 첫번째 트랜잭션에서 읽은 이 데이터는 유효하지 않은 데이터가 됨
반복할 수 없는 읽기(nonrepeatable read)
- 트랜잭션이 같은 질의를 두 번 이상 수행할 때 서로 다른 데이터를 얻게 되는 문제
- 보통 각 질의 수행 사이에 동시 진행 중인 다른 트랜잭션에서 이 데이터를 변경하는 경우에 발생함.
팬텀 읽기(phantom read)
- 어떤 트랜잭션(T1)이 둘 이상의 데이터 행을 읽은 다음, 동시 진행 중인 다른 트랜잭션(T2)이 추가 행을 삽입할 때 발생함.
- T1에서 동일한 질의를 다시 수행하면, T1은 이전에 없던 데이터 행까지 읽음.
격리 수준은 어떤 트랜잭션에 동시 진행하는 다른 트랜잭션이 영향을 미치는 정도를 결정. (기본값 : DEFAULT)
격리 수준 |
의미 |
더티 읽기 |
반복할 수 없는 읽기 |
팬텀 읽기 |
DEFAULT |
하부 데이터 저장소의 기본 격리 수준 이용함 |
- |
- |
- |
READ_UNCOMMITTED |
커밋되지 않은 데이터 변경사항을 읽을 수 있음 |
O |
O |
O |
READ_COMMITTED |
커밋된 데이터 변경사항만 읽을 수 있음 |
X |
O |
O |
REPREATABLE_READ |
트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정이 불가능하다. 선행 트랜잭션이 읽은 데이터는 트랜잭션이 종료될 때까지 후행 트랜잭션이 갱신하거나 삭제하는 것을 불허함으로써 같은 데이터 필드는 여러번 반복해서 읽더라도 동일한 값을 읽도록 함.
|
X |
X |
O |
SERIALIZABLE |
트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정이 불가능하다. 가장 성능이 비효율적인 격리 수준임 |
X |
X |
X |
DBMS별 기본값으로 많은 DBMS가 Read Committed를 기본 트랜잭션 격리성 수준으로 채택함
DBMS |
Isolation level 기본 값 |
Oracle |
READ COMMITTED |
MySql |
REPEATABLE READ (Inno DB) |
Mssql |
READ COMMITTED |
Cubrid |
REPEATABLE READ CLASS with READ UNCOMMITTED INSTANCES (READ UNCOMMITTED) |
✔︎ timeout (optional)
트랜잭션 시간을 설정하면, 해당 시간이 지나면 모든 처리 사항이 롤백된다.
propagation 방식이 REQUIRED 나 REQUIRES_NEW 일 경우에만 적용
✔︎ readOnly (optional)
Read-only 트랜잭션은 데이터를 수정하지 않고, 읽기만 할 경우 사용할 수 있다.
읽기 전용 트랜잭션은 하이버네이트를 사용하는 경우에 유용한 최적화 방법이다.
propagation 방식이 REQUIRED 나 REQUIRES_NEW 일 경우에만 적용
✔︎ rollbackFor
롤백해야 하는 예외 클래스를 지정
✔︎ rollbackForClassName
롤백해야 하는 예외 클래스를 지정
✔︎ noRollbackFor
롤백되지 않아야하는 예외 클래스를 지정
✔︎ noRollbackForClassName
롤백되지 않아야하는 예외 클래스를 지정
출처
reference/html/transaction.html
'Spring > JPA & Hibernate' 카테고리의 다른 글
JPA 쿼리 (0) | 2020.12.20 |
---|---|
연관관계 관리 (즉시 로딩과 지연 로딩) (0) | 2020.12.18 |
JPA 데이터 타입 (0) | 2020.01.15 |
영속성 전이 CASCADE (0) | 2020.01.10 |
proxy (0) | 2020.01.10 |