5/6 TIL

ehllll·2025년 5월 6일

-트랜잭션-

✅ 트랜잭션이란

여러 작업을 하나의 묶음처럼 처리해서, 모두 성공하거나 모두 실패하게 만드는것

현실 예시)

  • 송금 : 돈 보내는사람 계좌에서 돈 빠짐 -> 받는사람 계좌로 돈 들어옴
    ㄴ> 둘 중 하나라도 실패하면 전체 취소 필요
    " 전부 성공 혹은 전부 실패 "


✅ ACID (트랜잭션 발생시 안전성보장 위한 4가지 속성)

Atomicity (원자성)

ㄴ 모든 작업이 성공하거나 아무 작업도 일어나지 않음
예) 출금만 성공하고 입금실패하면 전체 롤백

Consistency (일관성)

ㄴ 하나의 트랜잭션 끝난 뒤에도 모든 상태는 이전과 같이 유효해야함
예) 돈이 어디 증발하지 않아야함

Isolation (격리성)

ㄴ 모든 트랜잭션은 다른 트랜젝션으로부터 독립적임
예) 동시에 송금해도 영향이 없어야함

Durability (지속성)

ㄴ 하나의 트랜잭션이 완료되었다면 영구적으로 저장
예) 성공한 거래는 절대 사라지지않아야함




✅ 트랜잭션 상태 (진행과정)

Active (진행중)

Partially Committed (커밋 직전)

Committed (성공적으로 완료)

Failed (중간에 문제 발생)

Aborted (실패 -> 롤백)




✅ 스프링에서 롤백

Checked Exception -> 기본은 롤백 X, 원하면 설정해야함

예) IOException

Unchecked Exception -> 기본적으로 롤백

예) RuntimeException



✅ 트랜잭션 내부 호출 문제

public void saveWithTx() {
    saveInnerMember(); // 내부 호출
}

ㄴ> 같은 클래스 안에서 메소드 부르면 스프링의 프록시가 개입을 못함
ㄴ> @Transactional이 적용되지않음



✅ 전파 속성 (Propagation)

REQUIRED : 있으면 기존거 사용, 없으면 새로 생성(기본)

REQUIRES_NEW : 기존 일시중단 후, 항상 새 트랜잭션 시작 (독립적인 작업처리)

SUPPORTS : 있으면 참여, 없으면 그냥 감

NOT_SUPPORTED : 무조건 트랜잭션 없이 감

MANDATORY : 무조건 기존 트랜잭션 있어야함

NEVER : 절대 트랜잭션 있으면 안 됨

NESTED : 부분 롤백 가능



✅ 격리 수준 (Isolation Level)

READ UNCOMMITTED

ㄴ 커밋 안된것도 읽음
ㄴ Dirty Read 발생

READ COMMITTED

ㄴ 커밋 된것만 읽음
ㄴ Non-Repeatable Read 발생

REPEATABLE READ

ㄴ 같은 데이터 계속 읽음
ㄴ MySQL 기본값

SERIALIZABLE

ㄴ 데이터 접근시 락을 걸어서 완전 차단
ㄴ 성능 느리지만 안전함

격리수준 높일수록 : 안정성⬆️ , 성능⬇️




✅ 스프링 @Transactional 어노테이션

ㄴ Java에서 지원하는 어노테이션, 스프링이 지원하는 어노테이션 2가지의 @Tx 어노테이션이 존재
ㄴ 트랜잭션의 기본기능은 동일하게 지원하지만 스프링의 어노테이션이 더 많은 기능을 지원해줌

jakara 보다는 스프링의 @Tx 어노테이션을 사용하는것이 더 좋음 (다양한 옵션을 세밀하게 조절 가능)





📍 최종 요약

트랜잭션 : 하나의 작업단위, 모두 성공하거나 모두 실패

롤백 : 실패하면 트랜잭션을 전부 취소

ACID : 트랜잭션이 지켜야할 4대 원칙

스프링 롤백 규칙 : Unchecked만 기본 롤백, Checked는 따로 설정 필요

전파(Propagation) : 트랜잭션중에 또 트랜잭션 시작할 때 규칙

내부 호출 문제 : 프록시 통과 안하면 트랜잭션 적용안됨

격리 수준 : 트랜잭션끼리 간섭 막는 강도






‼️ 트랜잭션 실습 문제

1. 트랜잭션 기본 개념

: 트랜잭션이 필요한 이유를 2가지 현실 세계 예시로 설명

ㄴ> 쇼핑몰 결제: 주문 -> 결제완료 -> 재고차감이 모두 성공해야 정상주문
ㄴ> 병원예약시스템 : 예약등록 -> 의사스케줄 차감 -> 병실배정 모두 성공해야함
⭐️ 모두성공 or 모두 실패


2. 스프링 롤백 동작

: 코드에서 트랜잭션이 롤백되는지 커밋되는지 예측

@Transactional
public void process() {
    orderRepository.save(order); // 정상 저장
    if (someCondition) {
        throw new IllegalArgumentException("문제 발생");
    }
}

ㄴ 롤백된다
ㄴ IllegalArgumentException은 RuntimeException(UncheckedException)이기 때문에 자동 롤백


3. Checked vs Unchecked

: IOException 발생시 트랜잭션은 기본적으로 롤백이 될까? 만약 롤백되게 하려면 어떤 설정을 해야할까?

ㄴ 롤백되지않음
ㄴ 기본설정에서는 Checked Exception이 롤백트리거가 아니기때문
ㄴ 롤백하려면 @Transactional에 rollbackFor 옵션을 줘야함
ㄴ> @Transactional(rollbackFor = IOException.class)


4. 전파 이해

: 다음 상황에서 어떤 Propagation 옵션을 사용해야 할까?

"주문 저장"은 기존 트랜잭션을 계속 사용하고, "결제 저장"은 항상 새로운 트랜잭션으로 따로 저장하고 싶다 (주문 실패하면 결제도 자동 롤백돼야 함)

어떤 전파 타입을 각각 써야 할까?

ㄴ 주문 : Propagation.REQUIRED(기존 트랜잭션 참여)
ㄴ 결제 : Propagation.REQUIRES_NEW(새 트랜잭션 시작)


5. 격리수준

: 가장 적절한 Isolation Level을 고르기

한 트랜잭션에서 조회했는데, 바로 다른 트랜잭션이 데이터를 수정한 후 커밋하면, 다시 조회했을 때 값이 달라진다(처음 조회한 값과 다름)

이 현상을 막기 위해 어떤 Isolation Level을 써야 할까?

ㄴ REPEATABLE READ
ㄴ REPEATABLE READ는 트랜잭션내에서 같은데이터는 항상 같은 결과를 보장함
ㄴ MYSQL 기본 트랜잭션레벨이기도 함

0개의 댓글