티스토리 뷰

반응형

 

1. @Transactional 어노테이션(선언적 트랜잭션)


[ @Transactional  어노테이션 ]

Spring에서는 클래스나 인터페이스 또는 메소드에 부여할 수 있는 @Transactional이라는 어노테이션을 제공하고 있다.

이 어노테이션이 붙으면 스프링은 해당 타깃을 포인트 컷의 대상으로 자동 등록하며 트랜잭션 관리 대상이 된다. 즉, 이 어노테이션을 통해 포인트 컷에 등록하고 트랜잭션 속성을 부여하는 것이다.

이렇듯 AOP를 이용해 코드 외부에서 트랜잭션의 기능을 부여해주고 속성을 지정할 수 있게 해주는 것을 선언적 트랜잭션(declarative transaction)이라고 한다. 반대로 TrnasactionTemplate이나 개별 데이터 기술의 트랜잭션 API를 이용해 직접 코드안에서 사용하는 방법을 프로그램에 의한 트랜잭션(programmatic transaction)이라고 한다. 스프링에서는 두 가지 방식을 모두 지원하지만, 특별한 경우가 아니라면 선언적 트랜잭션을 사용하는 것이 좋다.

왜냐하면 @Transactional 어노테이션을 이용하면 트랜잭션 속성을 메소드 단위로 다르게 지정할 수 있어 매우 세밀한 트랜잭션 속성 제어가 가능할 뿐만 아니라 직관적이므로 이해하기도 좋다.

대신 이 어노테이션을 이용하려면 @EnableTransactionManagement을 추가해주어야 한다.

 

 

[ @Transactional의 대체 정책(Fallback Policy) ]

만약 모든 메소드에 @Transactional이 붙어있으면 메소드가 상당히 더러워진다. 그래서 스프링은 메소드 외에도 클래스와 인터페이스에 어노테이션을 붙일 수 있도록 하고 있다. 그리고 트랜잭션 어노테이션을 적용할 때 타깃 메소드, 타깃 클래스, 선언 메소드, 선언 타입(클래스 or 인터페이스) 순으로 @Transactional이 적용되었는지 차례로 확인하고, 가장 먼저 발견되는 속성 정보를 사용한다. 이를 4단계의 대체 정책(fallback policy)라고 부르며, 이를 통해 어노테이션을 최소화하는 동시에 세밀한 제어를 해줄 수 있다.

 

 

 

2. Spring에서 트랜잭션의 사용법


 

[ Spring에서 트랜잭션의 활용법 ]

 

1. 비지니스 로직과의 결합

트랜잭션을 중구난방으로 적용하는 것은 좋지 않다. 대신 특정 계층의 경계를 트랜잭션 경계와 일치시키는 것이 좋은데, 일반적으로 비지니스 로직을 담고 있는 서비스 계층의 메소드와 결합시키는 것이 좋다. 왜냐하면 데이터 저장 계층으로부터 읽어온 데이터를 사용하고 변경하는 등의 작업을 하는 곳은 일반적으로 서비스 계층이기 때문이다. 위와 같이 클래스 레벨에 트랜잭션 어노테이션을 붙여주면 메소드까지 적용이 된다.

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder passwordEncoder;

    public List<User> getUserList() {
        return userRepository.findAll();
    }

}

 

서비스 계층을 트랜잭션의 시작과 종료 경계로 정했다면, 테스트와 같은 특별한 이유가 아니고는 다른 계층이나 모듈에서 DAO에 직접 접근하는 것은 차단해야 한다. 트랜잭션은 보통 서비스 계층의 메소드 조합을 통해 만들어지기 때문에 DAO가 제공하는 주요 기능은 서비스 계층에 위임 메소드를 만들어둘 필요가 있다. 그리고 가능하면 다른 모듈의 DAO에 접근할 때는 서비스 계층을 거치도록 하는 것이 바람직하다.

 

 

2. 읽기 전용 트랜잭션의 공통화

클래스 레벨에는 공통적으로 적용되는 읽기전용 트랜잭션 어노테이션을 선언하고, 추가나 삭제 또는 수정이 있는 작업에는 쓰기가 가능하도록 별도로 @Transacional 어노테이션을 메소드에 선언하는 것이 좋다.

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserService {

    private final UserRepository userRepository;
    private final BCryptPasswordEncoder passwordEncoder;

    public List<User> getUserList() {
        return userRepository.findAll();
    }

    @Transactional
    public User signUp(final SignUpDTO signUpDTO) {
        final User user = User.builder()
                .email(signUpDTO.getEmail())
                .pw(passwordEncoder.encode(signUpDTO.getPw()))
                .role(UserRole.ROLE_USER)
                .build();

        return userRepository.save(user);
    }

}

 

이를 통해 (체감하기는 힘들겠지만) 약간의 성능적인 이점을 얻을 수 있다.

 

 

3. 테스트의 롤백

트랜잭션 어노테이션을 테스트에 붙이면 테스트의 DB 커밋을 롤백해주는 기능이 있다.

DB와 연동되는 테스트를 할 때에는 DB의 상태와 데이터가 상당히 중요하다. 하지만 문제는 테스트에서 DB에 쓰기 작업을 하면 DB의 데이터가 바뀌는 것인데, 트랜잭션 어노테이션을 테스트에 활용하면 테스트를 진행하는 동안에 조작한 데이터를 모두 롤백하고 테스트를 진행하기 전의 상태로 만들어준다. 어떠한 경우에도 커밋을 하지 않기 때문에 테스트가 성공하거나 실패해도 상관이 없으며 심지어 예외가 발생해도 어떠한 문제가 발생하지 않는다. 강제로 롤백시키도록 설정되어 있기 때문이다.

@Transactional
@ExtendWith(SpringExtension.class)
@DataJpaTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void findByEmailAndPw() {
        final User user = User.builder()
                .email("email")
                .pw("pw")
                .role(UserRole.ROLE_USER).build();
        userRepository.save(user);

        assertThat(userRepository.findAll().size()).isEqualTo(1);
    }

}

 

하지만 테스트 메소드 안에서 진행되는 작업을 하나의 트랜잭션으로 묶고는 싶지만 강제 롤백을 원하지 않을 수 있다. 테스트의 작업을 그대로 DB에 반영하고 싶다면 @Rollback(false)를 이용해주면 된다. 하지만 @Rollback은 메소드에만 적용가능하므로, 클래스 레벨에 부여하기를 원한다면 @TransactionConfiguration(defaultRollback=false) 를 이용하고, 롤백을 원하는 메소드에 @Rollback(true)를 이용하면 된다.

 

물론 여기서 auto_increment나 sequence 등에 의해 증가된 값은 롤백이 되지 않는다. 그렇기 때문에 테스트를 위해서는 별도의 데이터베이스로 연결을 하거나 또는 H2와 같은 휘발성(인메모리) 데이터베이스를 사용하는 것이 좋다.

 

 

 

 

 

관련 포스팅

  1. 트랜잭션에 대한 이해와 Spring이 제공하는 Transaction(트랜잭션) 핵심 기술 - (1/3)
  2. Spring 트랜잭션의 세부 설정(전파 속성, 격리수준, 읽기전용, 롤백/커밋 예외 등) - (2/3)
  3. Spring에서 트랜잭션의 사용법 - (3/3)
반응형
댓글
댓글쓰기 폼
  • java개발자 2. 테스트의 롤백
    "조작한 데이터를 모두 롤백하고 테스트를 진행하기 전의 상태로 만들어준다." 라고 말씀하셨는데,
    auto_increment 또는 sequence로 증가된 값은 rollback이 되지 않는다고 알고 있습니다.

    그래서 테스트를 할 때는, 인메모리 DB를 이용해서 테스트를 하는 것으로 알고 있습니다.
    2021.07.15 15:17 신고
  • 망나니개발자 넵넵! 말씀해주신대로 해당 값들은 auto_increment 또는 sequence로 증가된 값은 rollback이 되지 않습니다. 그래서 실제로 테스트를 위해서는 h2와 같은 데이터베이쓰를 쓰죠!
    의도한 바는 디비에 데이터들이 insert되지 않음을 설명하고 싶었는데 충분히 오해할 소지가 있겠네요! 수정하도록 하겠습니다!
    좋은 지적 해주셔서 감사합니다:)
    2021.07.15 15:48 신고
반응형
공지사항
Total
1,479,759
Today
3,080
Yesterday
4,488
TAG more
«   2021/09   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    
글 보관함