@Transactional

  • 메서드나 클래스에 적용하여, 이 단위가 완전하게 실행되거나 완전히 롤백되도록 보장하는 어노테이션
  • 내부적으로 프록시라는 기술을 사용

 

프록시

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

 

프록시 실습

// TodoProjectApplicationTests.java

@Autowired
    private TodoService todoService;

    @Test
    void logIsProxy(){
        log.info("\nTodoService : {}", todoService.getClass());
    }

-> TodoService의 구현체 TodoServiceImpl이 정상적으로 주입

 

 

👇 TodoServiceImpl에 @Transactional 어노테이션을 적용하고 test

@Transactional
public TodoResponseDto addTodo(TodoRequestDto dto) {
    ...
}

-> TodoServiceImple이 Spring CGLIB 프록시 객체로 감싸져 있음

 

CGLIB

  • 클래스 기반으로 프록시 객체를 생성하는 방식
  • 기존 클래스를 상속해서 프록시를 생성

@Transactional 내부 동작 원리

  1. 해당 메서드가 호출되면 프록시 객체 생성
  2. 프록시 객체가 TransactionInterseptor 실행 
  3. TransactionInterceptor가 트랜잭션 설정
    • @Transactional에 지정한 속성에 따라서 적절한 트랜잭션 매니저 선택
    • 트랜잭션 매니저가 커넥션 생성
    • 커넥션 내부의 setAutoCommit(false) 실행 -> JDBC 연결의 자동 커밋을 비활성화
    • 트랜잭션 동기화 매니저에 커넥션 저장 -> 동일한 커넥션을 하나의 스레드에서 일관되게 사용하도록 보장
  4. 프록시 객체가 실제 타겟 메서드 호출
  5. TransactionInterceptor가 해당 트랜잭션 작업이 끝나면 commit / 예외가 발생하면 rollback

출처 : https://jaimemin.tistory.com/2271

 


 

@Transactional 사용 시 주의사항

1. 하나의 클래스에 A메서드와 @transactional이 적용된 B메서드가 있다고 가정할 때, A메서드가 B메서드를 호출하면 트랜잭션 적용 안 됨

원인 : 같은 클래스 내에서의 메서드 간 호출은 프록시를 거치지 않음

해결 방법 : 두 메서드를 다른 클래스로 분리

 

2. private 메서드에 적용 불가

-> CGLIB는 타겟의 클래스를 상속받아 프록시 생성하므로, private 메서드를 상속받을 수 없어 적용이 불가능하다

 


 

@Transactional 테스트

  • 롤백 테스트
  • @Transactional이 붙은 테스트는 종료 후 트랜잭션이 자동으로 롤백되어, 테스트 중 발생한 데이터 변경 사항이 데이터베이스에 반영되지 않는다.
  • 테스트 간 데이터 충돌을 방지할 수 있다.

 

 

JPA 변경 감지

엔티티 객체의 상태 변화를 추적해서 변경된 데이터를 DB에 자동으로 반영하는 기능

 

@Transactional이 있을 때

JPA 변경 감지를 통해 DB에 데이터가 잘 변경되는 것을 확인할 수 있으므로, 테스트 성공 예측이 가능하다.

테스트가 끝난 후 롤백해서 테스트 결과를 DB에 반영하지 않음

 

@Transactional이 없을 때 (기본 테스트 방식)

변경 감지를 안하기 때문에 DB에 데이터가 잘 변경되는지 확인할 수 없다.

만약 DB에 데이터가 잘 변경되는지 확인하려면 flush()를 실행해야한다.

flush()를 통해서 DB에 테스트 결과를 반영했다면, 테스트가 끝난 후에도 롤백되지 않는다.

 

👇

 

@Transactional 테스트 진행 시 주의사항

실제 서비스 코드에 @Transactional을 사용하지 않는 상황에서, 해당 메서드가 제대로 실행되지 않더라도

@Transactional을 적용한 테스트 코드는 JPA가 변경 감지를 하기 때문에 의도대로 작업이 진행된다고 예상할 수 있다.