19. [JPA] 트랜잭션 테스트

프로그래밍|2017. 11. 27. 16:59

JPA를 적용하고 나면 애플리케이션의 트랜잭션 처리가 정상 동작되는지 확인해 볼 필요가 있다.

예외가 발생했을 때 rollback 이 되는지를 확인하는 절차가 될 것이다.


스프링에서 지원하는 @Transactional을 이용해 보자.

@Transactional 은 선언적 트랜잭션이라 부른다.

클래스, 메서드위에 @Transactional 이 추가되면 프록시 오브젝트가 생성되어 요청이 들어왔을 때 이를 가로채고 트랜잭션 처리를 해준다. (메서드 동작 전, 후 처리)


예를 들어 사용자가 회원 정보 수정을 했다고 하자. 처리 순서는 다음과 같을 것이다.

1. 사용자가 주소 정보 변경 후 "수정" 버튼 클릭

2. 스프링의 Controller -> Service 호출 (updateMember 메서드 위에 @Transactional 추가되어 있다고 가정)

3. 프록시가 요청을 가로채고 트랜잭션 시작 (begin transaction)

4. updateMember 로직 처리

5. 프록시가 트랜잭션 종료 (커밋 또는 롤백)


대략 @Transactional 하는 역할에 대해서 알아보았으니 테스트를 통해 좀 더 자세히 알아보자.


[Member 엔티티]

1
2
3
4
5
6
7
8
9
@Data
@Table(name = "TRANSACTION_MEMBER")
@Entity
class Member {
    @Id
    private int id;
    @Column(name = "NAME")
    private String name;
}
cs



정상 커밋 테스트


다음과 같이 @Transactional을 추가하고 propagation(트랜잭션 전파) 설정을 REQUIRES_NEW 로 하였다.

REQUIRES_NEW 옵션은 occursNothing 메서드를 호출하기 직전에 이미 트랜잭션이 시작되었더라도 새로운 트랜잭션을 생성하겠다는 의미이다.

1. 정상케이스 메서드 시작하면서 트랜잭션 시작 (테스트 케이스에 @Transactional 추가되어 있으므로 트랜잭션 시작)

2. memberService의 occursNothing 메서드 호출

 > @Transactional에 REQUIRES_NEW 옵션으로 되어 있기 때문에 새로운 트랜잭션 시작

3. 새로운 트랜잭션이 생성되고 Member 엔티티의 name을 "Leenamkyu"로 변경

4. occursNothing 빠져나오면서 영속성 컨텍스트가 flush 되고 트랜잭션 커밋

5. Leenamkyu로 변경되었는지 검증


1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
@Transactional
public void 정상케이스() {
    memberService.occursNothing("Leenamkyu"1);
    Member member = em.find(Member.class1);
    assertThat("Leenamkyu", is(member.getName()));
}
 
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void occursNothing(String name, int id) {
    Member member = em.find(Member.class, id);
    member.setName(name);
}
cs



롤백 테스트 (uncheked exception)


occursUnCheckedException 메서드 안에서 Member 엔티티의 name을 수정하고, NPE 예외가 발생하였다.

이런 경우 영속성 컨텍스트는 flush 하고 rollback 처리한다.

데이터 검증 시 Leenamkyu 로 변경되지 않은 것을 확인할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
@Transactional
public void 롤백테스트_unchecked_exception() {
    try {
        memberService.occursUnCheckedException("Leenamkyu"1);
    } catch (RuntimeException ex) {
    }
 
    // unchecked Exception 예외 발생 시 롤백 처리되어짐
    Member member = em.find(Member.class1);
    assertThat("nklee1", is(member.getName()));
}
 
 
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void occursUnCheckedException(String name, int id) {
    Member member = em.find(Member.class1);
    member.setName(name);
    throw new NullPointerException();
}
cs



롤백 테스트 (cheked exception)


occursUnCheckedException 메서드 안에서 Member 엔티티의 name을 수정하고, FileNotFoundException 예외가 발생하였다.

이런 경우 영속성 컨텍스트는 flush 하고 rollback 처리할 거라 생각하지만 commit 한다.

스프링은 기본적으로 Unchecked Exception을 rollback 대상으로 지정한다.

NullPointerException은 Unchecked Exception이고

FileNotFoundException은 Checked Exception이다.

RuntimeException을 상속한 예외를 Unchecked Exception이라 한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
@Transactional
public void 롤백테스트_checked_exception() {
    try {
        memberService.occursCheckedException("Leenamkyu"1);
    } catch (Exception ex) {
    }
 
    // checked Exception 예외 발생 시 롤백 되지 않는다. (유의사항)
    Member member = em.find(Member.class1);
    assertThat("Leenamkyu", is(member.getName()));
}
 
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void occursCheckedException(String name, int id) throws FileNotFoundException {
    Member member = em.find(Member.class1);
    member.setName(name);
    throw new FileNotFoundException();
}
cs



롤백 테스트 (cheked exception 롤백 처리 방법)


Checked Exception 발생 시 롤백을 원한다면 다음과 같이 rollbackFor 옵션에 Exception.class를 지정해 주면 된다.


1
2
3
4
5
6
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void occursCheckedExceptionRollback(String name, int id) throws FileNotFoundException {
    Member member = em.find(Member.class, id);
    member.setName(name);
    throw new FileNotFoundException();
}
cs


댓글()