티스토리 뷰
트랜잭션이란?
트랜잭션(Transaction)
- 거래 라는 의미를 가짐
- 데이터베이스의 상태를 변화시키기 해서 수행하는 작업의 단위
예시로 보는 계좌 이체에서의 트랜잭션
- A가 B에게 10,000원을 송금한다.
- A의 계좌 10,000원 감소
- B의 계좌 10,000원 증가
- 만일 A의 계좌에서 돈만 감소하고, B의 계좌에 돈이 증가되지 않는다면? → 멸망~~~
이처럼 트랜잭션은 일련의 과정이 하나의 동작처럼 수행되어야 한다.
트랜잭션의 특징 → ACID
- 원자성(Atomicity) - 트랜잭션 내에서 실행된 작업은 하나처럼, 모두 성공 or 실패
- 일관성(Consistency) - 모든 트랜잭션은 일관성 있는 데이터베이스 상태를 유지
- 격리성(Isolation) - 동시에 수행되는 각 트랜잭션은 서로에게 영향을 미치지 않도록 격리, 격리 수준으로 관리
- 지속성(Durability) - 트랜잭션이 성공적으로 수행되면 항상 기록되야 함
트랜잭션 격리 수준(Isolation level)
- 트랜잭션의 격리성을 완벽하게 보장하려면 트랜잭션을 차례대로 수행해야 함
- 격리 수준
- Read Uncommitted(커밋되지 않은 읽기)
- Read Committed(커밋된 읽기)
- Repeatable Read(반복된 읽기)
- Serializable(직렬화 가능)
- 격리 수준
커밋과 롤백
- 커밋 - 모든 작업들을 정상적으로 처리하겠다고 확정하는 명령어로 처리과정을 DB에 영구 저장
- 롤백 - 작업 중 문제가 발생되어 트랜잭션의 처리과정에서 발생한 변경사항을 취소하는 명령어
@Transactional
@Transactional을 사용하기 이전 코드
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
private final UserRepository userRepository;
private final Converter converter;
private final EntityManagerFactory emf;
public PostDto findById(Long id) {
EntityManager entityManager = emf.createEntityManager(); //EntityManagerFactory에서 entityManager 가져오기
EntityTransaction transaction = entityManager.getTransaction(); // entityManager에서 transaction 생성
try {
transaction.begin();
PostDto postDto = postRepository.findById(id)
.map(converter::convertPostDto)
.orElseThrow(PostNotFoundException::new);
transaction.commit();
return postDto;
} catch (Exception e) {
if (transaction.isActive()) {
transaction.rollback();
}
throw new RuntimeException("transaction error", e);
} finally {
entityManager.close();
}
}
}
엔티티 매니저로부터 transaction을 받아와
try catch로 transaction을 실행한다. -> 성공하면 커밋, 실패하면 롤백
@Transactional 사용 코드
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
private final UserRepository userRepository;
private final Converter converter;
@Transactional(readOnly = true)
public PostDto findById(Long id) {
return postRepository.findById(id)
.map(converter::convertPostDto)
.orElseThrow(PostNotFoundException::new);
}
}
@Transactional을 사용하면 위 코드를 이렇게 줄일 수 있다.
Spring의 AOP(Aspect Oriented Programming) - 관점 지향 프로그래밍, 관심사를 나눠 로직을 분리하는 것
@Transactional의 세부 설정(propagation, isolation)
- propagation전파 - 이미 트랜잭션이 진행중일 때 추가 트랜잭션 진행을 어떻게 할지 결정하는 것종류 트랜잭션 존재 트랜잭션 미존재
REQUIRED(기본) 기존 트랜잭션 이용 신규 트랜잭션 생성 SUPPORTS 기존 트랜잭션 이용 트랜잭션 없이 수행 MANDATORY 기존 트랜잭션 이용 Exception 발생 NEVER exception이 발생한다 정상적으로 트랜잭션 없이 수행 NOT_SUPPORTED 트랜잭션이 종료될 때 까지 대기한 후 트랜잭션이 종료되고 나면 실행 트랜잭션 없이 로직이 수행 REQUIRES_NEW 현재 트랜잭션이 종료될 때 까지 대기한 후 새로운 트랜잭션을 생성하고 실행 신규 트랜잭션을 생성하고 로직을 실행 NESTED 현재 트랜잭션에 Save Point를 걸고 이후 트랜잭션을 수행 REQUIRED와 동일하게 신규 트랜잭션을 생성하고 로직이 수행 - 기본 값인 REQUIRED를 사용
- commit의 경우 추가 트랜잭션의 commit이 아닌, 기존 commit이 진행될 때 DB에 저장된다
- 기존 트랜잭션 ( 새로운 작업 )
- 문구와 함께 기존 트랜잭션을 이용한다.
- isolation
- Read Uncommitted(커밋되지 않은 읽기)
- 가장 낮은 격리 수준
- 다른 트랜잭션의 커밋되지 않은 데이터를 읽는다.
- Dirty reads 발생할 수 있음
- Dirty Read - 다른 트랜잭션에 의해 수정됐지만 아직 커밋되지 않은 데이터를 읽는 것
- Non-repeatable read와 Phantom read이 발생할 수 있음
- Phantom read
한 트랜잭션 내에서 같은 쿼리를 두 번 수행하였는데 첫 번째 쿼리에서 없던 유령 레코드 (Phantom Record)가 두 번째 쿼리에서 나타나는 현상 - Non-repeatable read
한 트랜잭션 내에서 동일한 Row를 두 번 read 하였는데 그 사이에 값이 변경되거나 삭제되어 결과가 다르게 나타나는 현상
- Phantom read
- Read Committed(커밋된 읽기)
- dirty reads를 방지
- Repeatable Read(반복된 읽기)
- dirty reads, Non-repeatable read 방지
- Serializable(직렬화 가능)
- 모든 동시성 부작용 막음
- 차례로 호출하기 때문에 매우 안좋은 효율
- Read Uncommitted(커밋되지 않은 읽기)