Build Up
- 트랜잭션은 DBMS의 상태를 변경할 수 있는 일련의 단위이다.
- 트랜잭션은 ACID라는 기본 특징을 가지고있다.
- 원자성: 쪼개어지지 않는 단위
- 일관성: 트랜잭션 후에는 일관된 상태를 유지
- 고립성: 다른 트랜잭션이 끼어들 수 없음
- 지속성: 성공적으로 완료되면 영구적으로 반영이 되어야 함
트랜잭션은 시작지점(CONNECTION)과 끝나는 지점(COMMIT, ROLLBACK)이 존재한다.
트랜잭션의 시작
트랜잭션은 하나의 커넥션을 가져와 사용하다가 닫는 사이에 일어난다.
트랜잭션의 시작과 종료는 커넥션 객체를 통해 이루어진다.
JDBC의 기본 설정은 DB 작업 수행 이후에 바로 커밋하는 옵션이 DEFAULT이다.
그러므로 JDBC에서 트랜잭션을 시작하려면 자동 커밋 옵션을 false로 해주어야하고 그러면 새로운 트랜잭션이 시작하게 만들 수 있다.
스프링을 이용하면 내부적으로 커넥션을 갖고 있는 추상화된 트랜잭션 매니저를 이용하게 된다.
public void executeQuery() throws SQLException { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); // 트랜잭션 시작 ... }
이때, 다음과 같이 트랜잭션을 시작하게 되고, 자동 커밋 옵션을 변경하는 등의 작업은 트랜잭션 매니저 내부에서 진행된다.
트랜잭션의 종료
하나의 트랜잭션이 시작되면 commit 도는 rollback 호출될 때 까지가 하나의 트랜잭션으로 묶인다.
public void executeQuery() throws SQLException { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); // 트랜잭션 시작 try { // 쿼리 실행 ... transactionManager.commit(status); } catch (Exception e) { transactionManager.rollback(status); } }
이처럼 setAutoCommit(false)로 트랜잭션의 시작을 선언하고 commit() 또는 rollback()으로 트랜잭션을 종료하는 작업하는 경계 설정이라고 한다.
트랜잭션의 경계는 하나의 커넥션을 통해 진행되므로 트랜잭션의 경계는 하나의 커넥션이 만들어지고 닫히는 범위 안에 존재한다.
What
트랜잭션의 전파속성
background
- Spring이 제공하는 선언적 트랜잭션의 장점중 하나는 여러 트랜잭션을 묶어서 하나의 트랜잭션 경계를 만들 수 있다는 점이다.
- 여러 작업을 하다보면 기존의 트랜잭션이 진행 중일 때 추가적인 트랜잭션을 진행해야 하는 경우 가 있다.
이미 트랜잭션이 진행 중일 때 추가 트랜잭션 진행을 어떻게 할지 결정하는 것이 전파속성이다.
새로운 트랜잭션 열어? 아니면 그대로 진행시켜? (트랜잭션 붙여)
물리 트랜잭션과 논리 트랜잭션 

트랜잭션은 데이터베이스의 커넥션 객체를 사용한다 라는 사실을 알고 있다.
1개의 트랜잭션을 사용한다는 것은 하나의 커넥션 객체를 사용한다는 것이고, 실제 DB의 트랜잭션을 사용한다는 점에서 물리 트랜잭션이라고도 한다.
트랜잭션 전파속성에 따라 외부 트랜잭션과 내부 트랜잭션이 동일한 트랜잭션을 사용할 수도 있다.
하지만 스프링 입장에서는 트랜잭션 매니저를 통해 트랜잭션을 처리하는 곳이 2곳이 있다.

이 그림은, 외부 트랜잭션과 내부 트랜잭션이 1개의 물리 트랜잭션(커넥션)을 사용하는 경우이다.
이 경우에는 2개의 트랜잭션 범위가 존재하기 때문에 개별 논리 트랜잭션이 존재하지만, 실제로 1개의 물리트랜잭션이 사용된다.
만약 트랜잭션 전파 없이 1개의 트랜잭션만 사용되면 물리트랜잭션만 존재하고 트랜잭션 전파가 사용될 때 논리 트랜잭션 개념이 사용된다.
물리 트랜잭션: 실제 데이터베이스에 적용되는 트랜잭션으로, 커넥션을 통해 커밋/롤백하는 단위
논리 트랜잭션: 스프링이 트랜잭션 매니저를 통해 트랜잭션을 처리하는 단위
기존의 트랜잭션이 진행중일 때 또 다른 트랜잭션이 사용되면 복잡한 상황이 발생한다. 스프링은 논리 트랜잭션의 개념을 도입함으로써 상황에 대한 설명을 쉽게 만들었고, 단순한 원칙을 세울 수 있다.
모든 논리 트랜잭션이 커밋되어야 물리트랜잭션이 커밋된다.
하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다.
이처럼 새로운 개념을 도입함으로써 2개 이상의 트랜잭션을 다루는 경우는 이해가 쉬워진다.
전파속성의 종류 (REQUIRED , REQUIRED_NEW)
REQUIRED (Default)
기본 속성으로 내부 트랜잭션은 기존에 존재하는 외부 트랜잭션에 참여하게 된다.( 트랜잭션을 이어버려!)

참여한다는 것은 외부 트랜잭션의 범위가 내부 까지 확장 된다는 뜻이다. 그러므로 내부 트랜잭션은 새로운 물리 트랜잭션을 사용하지 않는다.
물론, 내부 트랜잭션(논리)은 새로운 물리 트랜잭션을 사용하지 않지만 트랜잭션 매니저에 의해 관리되는 것이기 때문에 커밋은 내/외 부 해서 총 2회 실행되고 물리 트랜잭션은 1회 일어난다.
논리 트랜잭션은 단순히 스프링 내부에서 정의한 것이기 때문에 커밋을 호출해도 즉시 커밋되지 않고 외부 트랜잭션(마지막)이 최종적으로 커밋될 때 실제로 COMMIT이 이루어 진다.
롤백 역시 마찬가지로, 물리트랜잭션이 롤백될 때 실제 롤백을 하게 되는데, 논리 트랜잭션 중 1개라도 롤백되면 전부 롤백된다.
물리 트랜잭션은 실제 커넥션에 롤백/커밋을 호출하는 것이므로 해당 트랜잭션은 끝나게 된다.
REQUIRED_NEW
외부 트랜잭션과 내부 트랜잭션을 완전히 분리하는 속성이다.
총 2개의 물리 트랜잭션이 사용되며 각각의 트랜잭션 별로 커밋과 롤백이 수행된다.

물리 트랜잭션은 위 설명한 것처럼 실제 호출이므로, 내부 트랜잭션이 외부 트랜잭션의 롤백에 영향을 주지 않는다.
그러므로 내부 트랜잭션이 롤백 호출은 실제 커넥션에 롤백을 호출하는 것이므로 트랜잭션이 끝나게 된다.
서로 다른 물리 트랜잭션을 갖는 것은 각각의 DB 커넥션이 사용된다는 것이다.
즉 1개의 HTTP 요청에 대해 2개의 커넥션이 사용되는 것이다.
내부 트랜잭션이 처리 중일 때는 꺼내진 외부 트랜잭션이 대기하는데, 이는 DB 커넥션을 고갈 시킬 수 있다. 그러므로 잘 다루어야 한다.
NESTED
중첩(자식) 트랙잭션을 생성한다.
기존 트랜잭션이 없으면, 새로운 트랜잭션 생성
기존 트랜잭션이 있으면, 중첩 트랜잭션을 만듬
*NESTED는 이미 진행중인 트랜잭션에 중첩(자식) 트랜잭션을 만드는 것으로, 독립적인 트랜잭션을 만드는 REQUIRED_NEW 속성과는 다르다.
위에서는 독립적 트랜잭션은 즉 다른 물리 트랜잭션을 가지므로 외부에 영향을 주지 않는다.
부모 자식이라는 키워드를 가지고 보면, 중첩(inner) 트랜잭션이 롤백되면 외부 트랜잭션은 커밋이 가능하지만, 외부 트랜잭션이 롤백되면 중첩 트랜잭션 까지 롤백이 된다는 점에서 REQUIRED_NEW와 다르다.
NESTED는 JDBC의 savepoint 기능을 사용한다. 그러므로 DB Driver가 이를 지원하는지 확인이 필요하며 JPA에서는 사용이 불가능하다.
전체적 요약 Table

Why
- 여러 트랜잭션 작업을 하나의 트랜잭션으로 그룹핑 하기 위해 사용한다.
- 스프링 부트는 논리 트랜잭션이라는 개념을 도입함으로써 2개 이상의 트랜잭션을 다루는 경우가 편리해진다.
How
- @Transactional ( propagation = “${property}” )