[Spring Boot] 트랜잭션, 프록시

handa·2024년 12월 13일
0
post-thumbnail

1. 트랜잭션(Transaction)

트랜잭션은 데이터베이스에서 하나의 작업 단위를 말하며, 여러 작업이 하나의 논리적인 작업으로 실행되어야 할 때 사용됩니다. Spring Boot는 트랜잭션 관리를 쉽게 하기 위해 Spring Transaction Management를 제공합니다.

트랜잭션의 주요 특성 (ACID)

  1. 원자성(Atomicity) : 모든 작업이 완전히 완료되거나 아무 작업도 실행되지 않습니다.
  2. 일관성(Consistency) : 트랜잭션이 완료되면 데이터베이스는 항상 일관된 상태를 유지합니다.
  3. 고립성(Isolation) : 동시에 실행되는 트랜잭션은 서로 영향을 주지 않아야 합니다.
  4. 내구성(Durability) : 트랜잭션이 성공적으로 완료되면 변경 사항은 영구적으로 저장됩니다.

Spring Boot에서 트랜잭션 사용 방법

1. @Transactional 어노테이션

Spring에서 트랜잭션을 선언적으로 관리하기 위해 사용됩니다.
기본적인 예

@Service
public class UserService {

    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        // 예외 발생 시, 이전 작업은 롤백됩니다.
        if (user.getName().isEmpty()) {
            throw new RuntimeException("Name cannot be empty");
        }
    }
}

2. 트랜잭션 전파 속성 (Propagation)

트랜잭션의 전파 방식을 정의합니다.

  • REQUIRED : 기본 설정, 이미 진행 중인 트랜잭션이 있다면 이를 사용하고, 없으면 새로 시작합니다.
  • REQUIRES_NEW : 항상 새로운 트랜잭션을 시작하며, 기존 트랜잭션은 보류됩니다.
  • MANDATORY : 기존 트랜잭션이 없으면 예외를 발생시킵니다.
  • SUPPORTS : 트랜잭션이 있으면 사용하고, 없으면 트랜잭션 없이 실행합니다.

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void performAnotherTask() {
    // 새로운 트랜잭션에서 작업 수행
}

3. 트랜잭션 격리 수준 (Isolation)

트랜잭션이 다른 트랜잭션과 데이터를 공유하는 방식을 정의합니다.

  • READ_UNCOMMITTED : 커밋되지 않은 데이터를 읽을 수 있음.
  • READ_COMMITTED : 커밋된 데이터만 읽음.
  • REPEATABLE_READ : 트랜잭션 동안 동일 데이터를 읽을 수 있음.
  • SERIALIZABLE : 완벽한 격리, 동시 실행 불가.

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void processTransaction() {
    // 데이터 일관성 보장
}

2. 프록시(Proxy)

프록시는 트랜잭션 관리의 내부 구현에 중요한 역할을 합니다. Spring은 프록시 객체를 사용하여 트랜잭션의 시작, 커밋, 롤백 등을 처리합니다.

AOP 기반 트랜잭션 관리

Spring은 AOP(Aspect-Oriented Programming)를 통해 트랜잭션 관리를 구현합니다. @Transactional 어노테이션이 적용된 메서드에 대해 프록시 객체가 생성되며, 이 객체가 트랜잭션 경계를 관리합니다.

프록시 동작 과정

  1. 메서드 호출
    • 클라이언트가 UserServicecreateUser() 메서드를 호출.
  2. 프록시 인터셉션
    • 프록시가 메서드 호출을 가로챕니다.
  3. 트랜잭션 시작
    • 메서드 실행 전에 트랜잭션을 시작합니다.
  4. 원본 메서드 호출
    • 원본 UserService.createUser() 메서드가 실행됩니다.
  5. 트랜잭션 처리
    • 정상 완료 시 commit().
    • 예외 발생 시 rollback().

프록시 생성 방식
Spring은 두 가지 방식으로 프록시를 생성합니다.

  1. JDK 동적 프록시
    • 인터페이스 기반 프록시 생성
    • 해당 클래스가 인터페이스를 구현해야만 사용 가능.
  2. CGLIB(Code Generation Library)
    • 클래스 기반 프록시 생성.
    • 인터페이스가 없는 경우 사용되며, 메서드를 동적으로 오버라이드합니다.

3. 트랜잭션과 프록시의 한계

1. Self-Invocation(자체 호출) 문제

트랜잭션이 선언된 메서드가 같은 클래스의 다른 메서드에서 호출될 경우, 프록시가 트랜잭션 경계를 인식하지 못합니다.

문제 예

@Service
public class UserService {
    @Transactional
    public void methodA() {
        methodB(); // 트랜잭션 적용 안됨
    }

    @Transactional
    public void methodB() {
        // 트랜잭션 필요 작업
    }
}

해결 방법

  • 클래스를 분리하여 호출하거나, AOP 설정을 조정합니다.
  • 직접 TransactionTemplate 을 사용하여 트랜잭션을 수동 관리.

2. 비영속성 상태에서 트랜잭션 문제

Hibernate는 영속성 컨텍스트(EntityManager)에 의해 관리되는 엔티티에 트랜잭션이 적용됩니다. 트랜잭션이 종료되면 영속성 컨텍스트가 닫히므로, 이후 작업에서 Lazy Loading 문제가 발생할 수 있습니다.

해결 방법

  • FetchType.EAGER 로 설정하여 즉시 로딩.
  • 필요한 데이터를 트랜잭션 내에서 모두 로드.

4. 트랜잭션과 프록시의 장점

  1. 코드 간소화
    • @Transactional 로 선언적 트랜잭션 관리 가능.
  2. 유연성
    • 트랜잭션 전파와 격리 수준을 세밀하게 제어.
  3. 안정성
    • 프록시가 예외 발생 시 자동으로 롤백 처리

5. Spring 트랜잭션의 실제 동작 디버깅

  • 로그 확인 : spring.jpa.show-sql=truelogging.level.org.springframework.transaction=DEBUG 설정으로 트랜잭션 경계를 로그로 확인 가능합니다.

profile
진짜 해보자

0개의 댓글