기술 블로그

@Transactional과 프록시 패턴의 관계 본문

JAVA

@Transactional과 프록시 패턴의 관계

parkit 2025. 4. 16. 13:35
728x90
반응형

 

@Transactional과 프록시 패턴의 관계

@Transactional의 동작 방식을 프록시 패턴과 연결해서 설명해드리겠습니다.

프록시 패턴 기본 개념

프록시 패턴은 실제 객체를 대신하는 대리자(프록시) 객체를 사용하여:

  1. 실제 객체에 대한 접근을 제어하고
  2. 추가 기능을 제공하는 디자인 패턴입니다.

@Transactional과 프록시 패턴의 연결점

스프링에서 @Transactional을 사용하면 다음과 같은 일이 발생합니다:

  1. 스프링은 원본 객체를 감싸는 프록시 객체를 생성합니다
  2. 이 프록시는 실제 메소드 실행 전후에 트랜잭션 관련 코드를 추가합니다
  3. 클라이언트는 원본 객체 대신 이 프록시와 상호작용합니다

동작 과정 상세 설명

  1. 프록시 생성:
    • 스프링 컨테이너는 빈 초기화 시 @Transactional 애노테이션을 감지
    • 해당 빈에 대한 프록시 객체를 생성 (JDK 동적 프록시 또는 CGLIB)
    • 원본 빈 대신 프록시 객체를 컨테이너에 등록
  2. 메서드 호출 가로채기:
    • 클라이언트가 서비스 메서드를 호출하면 먼저 프록시 객체의 메서드가 호출됨
    • 프록시는 @Transactional 속성(전파 방식, 격리 수준, 타임아웃 등)을 확인
  3. 트랜잭션 시작:
    • 프록시는 PlatformTransactionManager를 통해 트랜잭션을 시작
    • 트랜잭션 정보를 ThreadLocal에 저장하여 동일 스레드에서 접근 가능하게 함
  4. 원본 메서드 실행:
    • 프록시가 실제 비즈니스 로직을 수행하는 원본 객체의 메서드를 호출
  5. 트랜잭션 종료:
    • 정상 실행 시: 트랜잭션 커밋
    • 예외 발생 시: 롤백 대상 예외인지 확인 후 롤백 (기본적으로 RuntimeException과 Error만 롤백)
    • 트랜잭션 리소스 정리 (데이터베이스 연결 반환 등)

예시 코드로 보는 @Transactional 동작 방식

1. 원본 서비스 클래스

@Service
public class UserService {
    
    private final UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        // 다른 로직들...
    }
}

2. 스프링이 내부적으로 생성하는 프록시 (개념적 코드)

public class UserServiceProxy extends UserService {
    
    private UserService target; // 실제 UserService
    private PlatformTransactionManager transactionManager;
    
    public UserServiceProxy(UserService target, PlatformTransactionManager transactionManager) {
        this.target = target;
        this.transactionManager = transactionManager;
    }
    
    @Override
    public void createUser(User user) {
        // 트랜잭션 시작
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        
        try {
            // 실제 비즈니스 로직 수행
            target.createUser(user);
            
            // 트랜잭션 커밋
            transactionManager.commit(status);
        } catch (Exception e) {
            // 예외 발생 시 롤백
            transactionManager.rollback(status);
            throw e;
        }
    }
}

3. 실제 사용 예시

@RestController
public class UserController {
    
    private final UserService userService; // 실제로는 UserServiceProxy가 주입됨!
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @PostMapping("/users")
    public ResponseEntity<Void> createUser(@RequestBody User user) {
        userService.createUser(user); // 프록시의 메서드가 호출됨
        return ResponseEntity.ok().build();
    }
}

@Transactional 동작 흐름도

┌─────────────────┐     1. 메서드 호출     ┌──────────────────────┐
│                 │─────────────────────▶│                      │
│   클라이언트     │                       │   @Transactional     │
│  (Controller)   │                       │     프록시 객체       │
│                 │◀─────────────────────│                      │
└─────────────────┘     8. 결과 반환       └──────────────────────┘
                                            │       ▲
                       2. 트랜잭션 시작      │       │ 7. 트랜잭션 완료
                                            ▼       │
                                         ┌──────────────────────┐
                                         │                      │
                                         │  PlatformTransaction │
                                         │      Manager         │
                                         │                      │
                                         └──────────────────────┘
                                            │       ▲
                    3. 트랜잭션 컨텍스트 생성 │       │ 6. 커밋/롤백
                                            ▼       │
         4. 메서드 호출       ┌──────────────────────┐
       ┌───────────────────▶│                      │
       │                    │   실제 서비스 객체     │
       │                    │ (UserServiceImpl)    │
       │                    │                      │
       │                    └──────────────────────┘
       │                              │
       │         5. DB 작업 실행       ▼
       │                    ┌──────────────────────┐
       │                    │                      │
       └────────────────────│      Database        │
                            │                      │
                            └──────────────────────┘

프록시 패턴을 활용한 @Transactional의 주요 흐름은 다음과 같습니다:

  1. 클라이언트(Controller)는 서비스 메서드를 호출하지만, 실제로는 프록시 객체의 메서드가 호출됩니다.
  2. 프록시는 TransactionManager를 통해 트랜잭션을 시작합니다.
  3. 트랜잭션 컨텍스트가 생성되고 현재 스레드에 바인딩됩니다.
  4. 프록시는 실제 서비스 객체의 메서드를 호출합니다.
  5. 서비스 객체는 데이터베이스 작업을 수행합니다.
  6. 작업이 완료되면 프록시로 제어가 돌아오고, 예외 발생 여부에 따라 커밋 또는 롤백됩니다.
  7. TransactionManager가 트랜잭션을 완료합니다.
  8. 프록시는 결과를 클라이언트에 반환합니다.
728x90
반응형