Transaction이란?

데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위

 

Transaction의 특징 + 관련 기능

✅ 원자성 Atomicity + Commit & Rollback

1개의 트랜잭션 내의 연산이 모두 반영되거나 또는 전혀 반영되지 않아야 한다.
  1. 초기 상태를 임시 영역 Rollback Segment에 저장한다.
  2. 트랜잭션 내에서 오류 발생 시,
    임시 영역에 저장한 상태로 Rollback
  3. 트랜잭션 내 연산 성공 시, Commit

✅ 일관성 Consistency 

트랜잭션 처리 결과는 항상 일관적이어야 한다.

 

✅ 격리성 Isolation + Lock

어떤 트랜잭션이 다른 트랜잭션의 연산에 끼어들 수 없다.

Race Condition(동시에 같은 데이터를 수정하려 할 때 발생하는 경쟁 상태)과 깊게 연관되어 있는 특성이다.

트랜잭션 1이 데이터를 읽거나 쓰기 작업 중일 때, 다른 트랜잭션이 접근하지 못하도록 Lock을 걸어
격리성을 지키고 Race Condition을 방지하는 것이다.

✅ 지속성 Durability

성공적으로 완료된 트랜잭션의 결과는 영구적으로 반영되어야 한다.

 


Spring에서의 롤백

위의 표와 같이 스프링에서 롤백은 발생한 예외가 런타임 도중에 발생한 예외인가 아닌가에 따라 다르게 처리된다.

  • Unchecked Exception (RuntimeException 또는 Error 계열)
    Rollback ⭕
    try-catch로 처리하여도 catch 여부와 무관하게 Rollback 한다.
  • Checked Exception
    Rollback ❌
    @Transactional rollbackFor에 명시적으로 설정해야 롤백된다.

 

트랜잭션을 관리하는 곳이 내부인지 외부인지에 따라서도 다르게 처리된다.

  • 외부에서 관리 (Propagation.REQUIRED)
    내부 트랜잭션에서 롤백 마킹이 되어도 외부의 최종 상태에 의존한다.
  • 내부에서 새로 생긴 트랜잭션인 경우 (Propagation.REQUIRES_NEW)
    내부 트랜잭션의 롤백은 외부에 영향을 주지 않는다.

https://techblog.woowahan.com/2606/

 

응? 이게 왜 롤백되는거지? | 우아한형제들 기술블로그

이 글은 얼마 전 에러로그 하나에 대한 호기심과 의문으로 시작해서 스프링의 트랜잭션 내에서 예외가 어떻게 처리되는지를 이해하기 위해 삽질을 해본 경험을 토대로 쓰여졌습니다. 스프링의

techblog.woowahan.com

 


Spring이 제공하는 Transaction

  • 동기화
  • 추상화
  • AOP 기반 분리

https://mangkyu.tistory.com/154

 

[Spring] 트랜잭션에 대한 이해와 Spring이 제공하는 Transaction(트랜잭션) 핵심 기술 - (1/3)

1. Transaction(트랜잭션)에 대한 이해 [ 트랜잭션(Transaction)의 필요성 ] 만약 데이터베이스의 데이터를 수정하는 도중에 예외가 발생된다면 어떻게 해야 할까? DB의 데이터들은 수정이 되기 전의 상

mangkyu.tistory.com

 

☑️ 트랜잭션 동기화

개발자가 직접 여러 개의 작업을 하나의 트랜잭션으로 관리하려면 Connection 객체를 공유하는 등의 작업이 필요하다.
Spring은 Connection 객체를 특별한 저장소에 보관해 두고 필요할 때 꺼내쓸 수 있도록 하는 기술을 제공한다.

  • 스레드마다 독립적으로 동기화 저장소의 Connection 객체를 관리한다.

 

☑️ 트랜잭션 추상화

Spring은 JDBC 종속적인 트랜잭션 동기화 코드들로 인해 유발되는 문제를 해결하기 위해 트랜잭션 관리 부분을 추상화하였다.

종속적이다. 
= JDBC는 Connection 기반 트랜잭션이지만 JPA는 EntityManager, Hibernate는 Session 기반
= 사용하는 리소스가 다름 

https://mangkyu.tistory.com/154

PlatformTransactionManager를 통해 사용하는 기술과 무관하게 트랜잭션을 일관적으로 관리할 수 있다!

☑️ AOP 기반 분리

트랜잭션 관리 코드들이 비즈니스 로직 코드와 결합되어 2가지 이상의 책임을 가지게 되는 경우 이를 어떻게 분리할 수 있을까?
바로 AOP를 이용하는 것이다! 

  • 트랜잭션 코드 같은 부가 기능 코드가 존재하지 않는 것처럼 보이게 한다.
  • 클래스에서 분리하여 별도의 모듈로 만든다.

 

트랜잭션 처리 과정

https://curiousjinan.tistory.com/entry/spring-transactional-propagation#1.%20%EC%89%BD%EA%B2%8C%20%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94%20%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-1

 

  1. Controller
    클라이언트의 요청에 따라 Controller가 호출되면 처리에 필요한 서비스를 호출함

  2. TransactionInterceptor 동작
    @Transactional 명시된 경우
    Spring AOP가 TransactionInterceptor로 메서드 호출을 가로챔 
    TransactionAttributeSource로 트랜잭션 속성 파악

  3. TransactionAttribute 추출 & 해석
    TransactionAttributeSource 구현체인 AnnotationTransactionAttributeSource는 리플렉션을 사용하여 메서드의 @Transactional을 찾아 분석
    분석 결과를 TransactionAnnotationParser 구현체인 SpringTransactionAnnotationParser TransactionAttribute로 변환

  4. 트랜잭션 시작 또는 참여 결정
    TransactionInterceptor는 현재 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하거나, 없으면 새로운 트랜잭션을 시작

  5. 서비스 메서드 실행

  6. 트랜잭션 커밋 또는 롤백

 


 

Spring 트랜잭션 세부 설정

💡트랜잭션 전파

어떤 트랜잭션이 동작 중인 과정에서 다른 트랜잭션을 실행할 경우 어떻게 처리하는 가

https://sjh9708.tistory.com/243

 

[Spring Boot] 트랜잭션 범위와 전파 속성(Propagation)

트랜잭션은 작업에서 예외가 발생할 경우 Rollback 처리를, 모두 성공할 경우 Commit 처리하는 실행 단위이다.Spring에서는 트랜잭션과 관련된 기술들을 제공하며, 주로 @Transactional 어노테이

sjh9708.tistory.com

 

Propagation.REQUIRED (기본값)
기존 트랜잭션이 있으면 사용하고 없으면 새로 만든다.

  • 대부분의 비즈니스 로직

Propagation.REQUIRES_NEW
항상 새로운 트랜잭션이 필요하다.

  • 독립적인 작업 처리

Propagation.NESTED
중첩 트랜잭션을 생성한다.

  • 하위 메서드에서 예외 발생 시, 중첩된 트랜잭션만 롤백
  • 임시 저장같이 부분적으로 롤백이 가능한 작업

Propagation.MANDATORY
트랜잭션이 의무이다.

  • 기존 트랜잭션이 없다면 IllegalTransactionStateException 발생
  • 트랜잭션 내부에서만 호출 가능한 메서드에서 사용

Propagation.SUPPORTS
기존 트랜잭션이 있으면 사용하고 없으면 새로 만들지는 않고 없이 진행한다.

  • 트랜잭션이 없는 상태에서 하위 메서드에서 예외가 발생 시, 트랜잭션과 관계없이 예외 처리
  • 트랜잭션이 필수가 아닌 작업

Propagation.NOT_SUPPORTED
트랜잭션 미지원, 기존에 트랜잭션이 있어도 이를 중지하고 없는 상태로 실행한다.

  • 로그 저장 등 트랜잭션과 독립적인 작업

Propagation.NEVER
트랜잭션 미지원, 상위 스코프에도 트랜잭션이 설정돼 있으면 안 된다.

  • 트랜잭션 설정 시, IllegalTransactionStateException 발생
  • 외부 시스템 호출 작업

 

❗@Transaction 전파 주의사항

한 서비스의 메서드에서 두 개 이상의 트랜잭션을 생성하지 않도록 하라!

@Transactianal은 프록시 기반으로 동작하기 때문에 외부에서 접근할 때 AOP를 통해서 프록시 객체를 접근할 수 있다.

    @Transactional
    public Long A() {
        return B();
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Long B() {
        return ...;
    }

위의 코드와 같이 A 메서드 내에서 REQUIRES_NEW가 선언된 B 메서드를 호출할 때,
별도의 트랜잭션이 생성되어 B가 실행될까?

아니다!

클래스내에서 다른 메서드를 호출하게 되면 프록시로 접근하지 않고 직접 접근하기 때문에
메서드에 선언해 놓은 @Transactinal이 정상적으로 동작하지 않는다.

별도의 트랜잭션을 사용하고자 한다면
별도의 서비스를 만들어주어야 한다!

https://velog.io/@myspy/Transaction-%EC%A0%84%ED%8C%8C%EA%B0%80-%EB%AD%A1%EB%8B%88%EA%B9%8C

 

Transaction 전파가 뭡니까?

이전 포스팅에서 Transactional을 학습한 이후에 Transactional 전파에 대한 학습을 약속했기 때문에 Transactional 전파라는 주제로 추가학습을 진행하게 됐다. 어떤 트갠잭션이 동작중인 과정에서 다른

velog.io

 


💡격리 수준 (DB마다 다름 주의)

서버에서는 여러 개의 트랜잭션이 동시에 진행될 수 있다.
그러나, 모든 트랜잭션을 독립적이고 순차적으로 진행한다면 안정적이지만 성능이 크게 떨어진다.

따라서 적절하게 격리수준을 조정해서 가능한 많은 트랜잭션을 동시에 진행시키면서 문제가 발생하지 않도록 제어해야 한다.
= 일관성이 깨지는 것을 어느 정도까지 허용하는가
Dirty Read : 다른 트랜잭션에 의해 수정되었지만, 아직 커밋되지 않은 상태의 데이터를 읽는 것

Non-Repeatable Read : 하나의 트랜잭션에서 같은 키를 가진 데이터를 두 번 읽을 때, 그 결과가 다르게 나타나는 현상

Phantom Read : 일정 범위의 데이터를 여러번 읽을 때, 처음에는 없던 유령(Phantom) 데이터가 이후에는 나타나는 현상

READ UNCOMMITTED = 열린문
아직 커밋되지 않은 데이터도 다른 트랜잭션에서 접근 가능
Dirty Read 발생 가능!
Non-Repeatable Read 가능!
Phantom Read 가능!

READ COMMITTED
커밋된 데이터만 다른 트랜잭션에서 읽기 가능
Non-Repeatable Read 가능!
Phantom Read 가능!

REPEATABLE READ
트랜잭션 내에서 같은 조건으로 같은 레코드를 여러 번 조회했을 때 항상 같은 결과를 보장
데이터 변경 전의 레코드를 UNDO 공간에 백업,
그 사이에 다른 트랜잭션이 커밋되었더라도 UNDO 공간에 백업해놓은 데이터를 참조해 항상 같은 조회 결과를 보장
Phantom Read 가능!

SERIALIZABLE = 넌 못 지나간다.
데이터 접근 시 락을 걸어서 같은 데이터에 다른 트랜잭션이 동시에 접근할 수 없는 격리 수준
가장 안전하지만 가장 성능이 떨어짐

읽기 전용 readOnly

  • 쓰기 지연(Dirty Checking) 기능 비활성화, flush 시점에 INSERT, UPDATE, DELETE SQL 생성 금지로 인한 최적화
  • 쓰기 작업 발생을 의도적으로 방지

읽기 전용 트랜잭션이 시작된 이후 INSERT, UPDATE, DELETE의 작업이 진행되면 예외가 발생한다.

그래서 일단 서비스 클래스 상단에 @Transaction(readOnly = true)를 명시한 후,
쓰기 작업이 필요한 메서드의 경우에만 @Transaction을 써주는 습관도 좋다.

@Service
@Transactional(readOnly = true) // 기본적으로 모든 메서드는 읽기 전용
public class MemberService {

    // 쓰기 메서드 → 트랜잭션 재정의
    @Transactional
    public Long registerMember(Member member) {
        memberRepository.save(member);
        return member.getId();
    }
}

+ Recent posts