[CS] Spring_ @Transactional

말하는 감자·2025년 1월 13일

CS

목록 보기
10/33
post-thumbnail

트랜잭션

1. 정의

데이터베이스 관리 시스템 또는 유사한 시스템에서 상호작용의 단위. 여기서 유사한 시스템이란 트랜잭션이 성공과 실패가 분명하고 상호 독립적이며, 일관되고 믿을 수 있는 시스템을 의미한다.

목적

  • 오류로부터 복구를 허용하고 데이터베이스를 일관성있게 유지하는 안정적인 작업 단위 제공
  • 동시 접근하는 여러 프로그램 간 격리를 제공

2. ACID

데이터베이스 시스템은 각각의 트랜잭션에 대해 원자성,일관성, 독립서으, 영속성을 보장한다.

  • Atomictiy(원자성): 트랜잭션이 완전히 성공하거나 실패하는 단일 단위로 처리되도록 보장하는 능력. -> 중간 단계까지 실행되고 실패하는 일 x
  • Consistency(일관성): 각 데이터 트랜잭션이 데이터베이스를 일관성 있는 상태에서 일관성 있는 사태로 이동해야 함을 의미한다. 즉 트랜잭션이 성공적으로 완료하면 언제나 동일한 데이터베이스 상태로 유지하는 것을 의미
  • Isolation(독립성): 트랜잭션 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장하는 것 의미
  • Durability(영속성): 성공적으로 수행된 트랜잭션은 영원히 기록되어야 함을 의미한다.

3. 트랜잭션 과정

  1. 트랜잭션 시작
  2. 비즈니스 로직 실행(DB내 갱신 적용x)
  3. 트랜잭션 커밋(트랜잭션이 성공하고, 갱신 적용)
  • 만약 쿼리 하나가 실패하면, 데이터베이스 시스템은 트랜잭션 또는 실패한 쿼리를 롤백

@Transactional

개념

클래스나 메서드에 붙여 선언적으로 트랜잭션을 관리하고 작업의 성공 여부에 따라 자동으로 커밋 또는 롤백 을 처리하는 어노테이션

소스코드에 직접 트랜잭션 관리 로직을 넣어두지 않고 비즈니스 로직에서 완전히 분리하는 방식

이 방식을 사용하면 프로그래밍에 의한 트랜잭션에서 나온 단점인 트랜잭션 코드 중복 문제, 소스코드 유지보수의 문제를 모두 해결할 수 있다
.
트랜잭션이라는 횡단 관심사를 비즈니스 로직에서 완전히 분리하기 때문에 SRP 관전에서 봤을때 적합하고 많은 양의 트랜잭션 로직을 적용하기에도 합리적

트랜잭션 격리 수준(Isolation Level)

  1. DEFAULT: 데이터베이스에서 설정된 기본 격리 수준
  2. READ_UNCOMMITED: 트랜잭션이 아직 커밋되지 않은 데이터를 읽을 수 있다.
  3. READ_COMMITED: Dirty Read를 방지하기 위해 Commit된 데이터만 읽을 수 있다.
  4. REPEATABLE READ: 트랜잭션이 완료될 때까지 조회한 모든 데이터에 shared lock이 걸리므로 트랜잭션이 종료될 때까지 다른 트랜잭션은 그 영역에 해당하는 데이터를 수정할 수 없다.
  5. SERIALIZABLE: 가장 엄격한 트랜잭션 격리 수준으로, 완벽한 읽기 일관성 모드를 제공한다. 이 격리 수준에서는 PHANTO READ가 발생하지 않지만 동시성 처리 성능이 급격히 떨어질 수 있다.

트랜잭션 전파 옵션(Propagation)

  1. REQUIRED: 이미 시작된 트랜잭션이 있으면 참여하고, 없으면 트랜잭션을 시작(디폴트 속성)
  2. SUPPORTS: 이미 시작된 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 처리
  3. REQUIRED_NEW: 항상 새로운 트랜잭션 시작. 이미 진행중인 트랜잭션이 있다면 잠시 보류
  4. MANDATORY: 이미 시작된 트랜잭션이 있으면 참여하고 없으면 새로운 트랜잭션을 시작하는 대신 예외를 발생. 독립적으로 수행되면 안되는 경우에 사용
  5. NOT_SUPPORTED: 트랜잭션을 사용하지 않고 처리하도록 한다. 진행중인 트랜잭션이 있다면 잠시 보류
  6. NEVER: 트랜잭션을 사용하지 않도록 강제한다. 이미 진행중인 트랜잭션 또한 허용하지 않으며 있다면 예외를 발생
  7. NESTED: 이미 실행중인 트랜잭션이 있다면 중첩하여 트랜잭션을 진행한다. 부모 트랜잭션은 중첩 트랜잭션에 영향을 주지만 중첩 트랜잭션은 부모 트랜잭션에 영향을 주지 않는다.

readOnly 옵션

  • readOnly 속성을 통해 트랜잭션을 읽기 전용으로 설정할 수 있다.
  • JPA의 경우 헤당 옵션을 true로 설정하게 되면 트랜잭션이 커밋되어도 영속성 컨텍스트를 플러시(동기화)하지 않는다. 플러시할 때 수행되는 엔티티의 스냅샷 비교로직이 수행되지 않으므로 성능을 향상시킬 수 있다.

@Transactional 작동

프록시

실제 객체에 대한 대리 객체를 생성하여 호출을 가로채고 추가 작업(로깅,트랜잭션 등)을 수행한 후 실제 객체에 전달하는 기술

@Transactional은 프록시를 생성하여 빈으로 등록

1. @Transactional 메서드 호출 감지


TransactionInterceptor가 메서드 호출을 가로채 트랜잭션 준비

2. 트랜잭션 매니저 결정


메서드와 클래스의 트랜잭션 속성에 따라 적절한 트랜잭션 매니저를 선택

커넥션 생성


데이터 소스를 통해 커넥션 생성

3. 트랜잭션 시작


시작준비

JDBC 연결의 자통 커밋 비활성화

트랜잭션을 명시적으로 시작하고 관리할 준비를 완료

4. 트랜잭션 동기화 매니저에 커넥션 저장


트랜잭션동안 동일한 커넥션을 하나의 스레드에서 일관되게 사용하도록 보장

@Transactional 내부 처리 과정

@Transactional 사용 시 주의점

proxy 내부 호출



외부에서 external을 호출하면 internal에 트랜잭션이 적용이 안된다.
같은 클래스 내에서 메서드간 호출은 프록시를 거치지 않기 때문에 트랜잭션이 적용되지 x

해결: 프록시 내부 호출을 피하기 위해 internal()을 별도의 클래스로 분리한다.

private 메서드 적용 불가

타겟의 클래스를 상속받아 프록시를 생성한다. -> private는 상속받은 하위 클래스에서 접근할 수 없는 문제

의도치 않은 트랜잭션 적용

  • 트랜잭션이 적용되지 않은 서비스 메서드
  • 트랜잭션이 적용된 테스트 코드

실제 서비스 코드에는 적용되지 않은 @Transactional 과 테스트 코드에 사용한 @Transactional로 인하여 테스트가 성공하지만, 실제 환경과 테스트 환경의 불일치로 인한 예상치 못한 상황이 발생할 가능성이 있다.

장/단점

장점

  • 명시적으로 DB를 초기화하는 작업 없이 손쉽게 롤백이 가능하다.
  • DB를 초기화하는 작업에 비해 테스트 성능이 빠르다

단점

  • 실제 서버환경과 다르게 동작할 수 있다.
  • 의도치 않은 트랜잭션이 테스트코드에 적용될 수 있다.
profile
주니어개발자(?)

0개의 댓글