만약에 데이터베이스에서 데이터를 수정할 때, 에외가 발생한다면 데이터베이스의 데이터들을 수정하기 전 상태로 돌아가야 하며, 다시 수정 작업을 진행해야 합니다. 이렇게 여러 작업을 진행하다가 예외가 발생하였을때, 이전 상태로 롤백하기 위해 사용하는 것을 트랜잭션이라 합니다. 간단히 말하면 Transaction(트랜잭션)이란 쪼갤 수 없는 여러 작업들을 논리적으로 묶은 가장 작은 단위입니다. 트랜잭션 작업의 마무리는 크게 2가지로 나뉩니다.
원자성(Atomicity)
트랙잭션의 과정을 데이터베이스에 모두 반영되던지, 모두 반영되지 않아야 합니다. 하나라도 실패한다면 앞서 성공한 것들을원상 복구 시켜야합니다.
일관성(Consistency)
트랜잭션의 작업 처리 결과는 항상 일관성 있어야 합니다.
독립성(Isolation)
어떤 트랜잭션도 다른 트랜잭션의 작업에 끼어들 수 없습니다.
지속성(Durability)
트랜잭션이 완료된다면, 결과는 영구적이어야 합니다.
스프링부트에서는 @Transaction 어노테이션을 이용하여 간단하게 사용가능합니다.
이 방식을 선언적 트랜잭션이라 부릅니다. 대부분 클래스나 메서드 수준에서 추가해 사용하는 방식이 일반적입니다. 적용된 범위에서는 트랜잭션 기능이 포합된 프록시 객체가 생성되어 자동으로 커밋,롤백을 진행해줍니다.
@Transactional
public void addProduct(Product product) throws Exception {
// 로직 구현
}
isolation -> 트랜잭션에서 일관성없는 데이터 허용 수준을 설정합니다.
propagation -> 트랜잭션 동작 도중 다른 트랜잭션을 호출할 때, 어떻게 할 것인지 지정하는 옵션입니다.
noRollbackFor -> 특정 예외 발생 시 rollback을 하지 않습니다.
rollbackFor -> 특정 예외 발생 시 rollback을 진행합니다.
timeout -> 지정한 시간 내에 메소드 수행이 완료되지 않으면 rollback을 진행합니다. (-1일 경우 timeout을 사용하지 않는다)
readOnly -> 트랜잭션을 읽기 전용으로 설정합니다.
@Transactional(isolation=Isolation.DEFAULT)
: 기본 격리 수준이며, 데이터베이스의 격리레벨을 따릅니다.
: 어떤 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 B라는 아직 완료되지 않은(Uncommitted 혹은 Dirty)데이터 B를 읽을 수 있습니다.
-Problem1 - Dirty Read 발생
: 어떠한 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 해당 데이터에 접근할 수 없습니다.
-Problem1 - Dirty Read 방지
: 트랜잭션이 완료될 때까지 조회를 하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정이 불가능 합니다.
: 선행 트랜잭션이 읽은 데이터는 트랜잭션이 종료될 때까지 후행 트랜잭션이 갱신하거나 삭제가 불가능 하기 때문에 같은 데이터를 두 번 쿼리했을 때 일관성 있는 결과를 리턴합니다.
-Problem2 - Non-Repeatable Read 방지
: 데이터의 일관성 및 동시성을 위해 MVCC(Multi Version Concurrency Control)을 사용하지 않습니다.
(MVCC는 다중 사용자 데이터베이스 성능을 위한 기술로 데이터 조회 시 LOCK을 사용하지 않고 데이터의 버전을 관리해 데이터의 일관성 및 동시성을 높이는 기술입니다)
: 트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능합니다.
-Problem3 - Phantom Read 방지
당연히 레벨이 높아질 수록 데이터 무결성을 유지할 수 있습니다.
그러나, 무조건적으로 상위 레벨을 사용한다면 Locking으로인해 동시에 수행되는 많은 트랜잭션들이 순차적으로 처리하게 되면서 DB의 성능은 떨어지게 되며 비용이 높아집니다. 그렇다고 Locking의 범위를 줄이게 된다면 잘못된 값이 처리될 상황도 발생한다.
그렇기 때문에 최대한 효율적인 방안을 찾아 상황에 맞게 사용하는 것이 중요합니다.
@Transactional(propagation=Propagation.REQUIRED)
이미 진행중인 트랜잭션이 있다면 해당 트랜잭션 속성을 따르고, 진행중이 아니라면 새로운 트랜잭션을 생성합니다.
항상 새로운 트랜잭션을 생성한다. 이미 진행중인 트랜잭션이 있다면 잠깐 보류하고 해당 트랜잭션 작업을 먼저 진행합니다
이미 진행 중인 트랜잭션이 있다면 해당 트랜잭션 속성을 따르고, 없다면 트랜잭션을 설정하지 않습니다.
이미 진행중인 트랜잭션이 있다면 보류하고, 트랜잭션 없이 작업을 수행합니다.
이미 진행중인 트랜잭션이 있어야만, 작업을 수행합니다. 없다면 Exception을 발생시킵니다.
트랜잭션이 진행중이지 않을 때 작업을 수행합니다. 트랜잭션이 있다면 Exception을 발생시킵니다.
진행중인 트랜잭션이 있다면 중첩된 트랜잭션이 실행되며, 존재하지 않으면 REQUIRED와 동일하게 실행됩니다.
@Transactional(noRollbackFor=Exception.class)
특정예외 발생 시 Rollback 처리 하지 않습니다.
@Transactional(rollbackFor=Exception.class)
특정 예외 발생 시 강제로 Rollback
timeout (시간지정)
@Transactional(timeout=10)지정한 시간 내에 해당 메소드 수행이 완료되지 않을 경우 rollback 수행
-1일 경우 no timeout
Default = -1
@Transactional(readonly = true)
true 시 insert, update, delete 실행 시 예외 발생
Default = flase
@Transactional은 기본적으로 Unchecked Exception, Error 만을 rollback하고 있습니다. 그렇기 때문에 모든 예외에 대해서 rollback을 진행하고 싶을 경우
(rollbackFor = Exception.class) 를 붙여야 합니다.
그 이유는 Checked Exception 같은 경우는 예상된 에러이기 때문이고,
Unchecked Exception, Error 같은 경우는 예상치 못한 에러이기 때문입니다.
트랜잭션의 많은 내용이 더 있지만, 당장 스프링부트를 사용하면서 궁금한 내용을 포스팅 해보았습니다.!!
참고
1. https://devkingdom.tistory.com/287
2. https://velog.io/@pp8817/%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%B6%94%EC%83%81%ED%99%94-%EB%8F%99%EA%B8%B0%ED%99%94
3. https://velog.io/@byeongju/Spring-Transaction%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90