Springboot test @Transactional rollback is not working

2022. 9. 26. 09:26Spring

728x90

문제 인식

 

@Transactional과 @Test 어노테이션을 사용하면, test할 시에 rollback이 되어야 하는 데(db에 테스트 값이 저장되지 않아야 하는데) rollback이 되지 않았다.

 

 

현재 상황

@SpringBootTest
@Transactional
class JdbcTemplateMemberRepositoryTest {

    @Autowired
    private JdbcTemplateMemberRepository jdbcTemplateMemberRepository;

    @Test
    public void save() {
        // given
        Member member = new Member();
        member.setMemberName("철수");
        member.setMemberId(180000);
        member.setMemberPassword("123@");
        member.setMemberNumber("01032431257");
        member.setSex(1);
        member.setGrade(Grade.NORMAL);
        jdbcTemplateMemberRepository.save(member);

        //when
        Member result = jdbcTemplateMemberRepository.findById(member.getMemberId()).get();

        // then
        Assertions.assertEquals(member.getMemberName(), result.getMemberName());
    }
}

 

문제가 발생한 부분을 예상해보자면...

  1. @Transactional 어노테이션
  2. org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false 로그에 써있던 것들 중에 유일하게 false라고 써있었던 것
  3. springboot test 문서에 있던 에러가 발생했을 경우 예상되는 부분에 대한 해석

프록시 모드(기본값)에서는 프록시를 통해 들어오는 외부 메서드 호출만 가로챕니다. 즉, 자체 호출은 실제로 대상 개체의 다른 메서드를 호출하는 대상 개체 내의 메서드가 호출된 메서드가 @Transactional로 표시되더라도 런타임에 실제 트랜잭션으로 이어지지 않음을 의미합니다. 또한 예상되는 동작을 제공하려면 프록시를 완전히 초기화해야 하므로 초기화 코드(예: @PostConstruct)에서 이 기능에 의존해서는 안 됩니다.”

 

 

1. @Transactional

@Transactional 어노테이션에는 'rollbackFor'라는 속성이 있는데, 이 속성값의 기본값은 

RuntimeException.class, Error.class이다.

 

코드로 나타낸다면 @Transactional(rollbackFor = {RuntimeException.class, Error.class})이다.

=> RuntimeException의 계열의 예외와 에러만 rollback 시키겠다 라는 의미이다.

 

그렇다면, 내가 사용했던 save()메소드를 살펴보자

 

save()메소드

 

public void save(Member member) {
        jdbcTemplate.update("insert into member(memberId,sex,memberPassword,memberName,memberNumber,grade) values(?,?,?,?,?,?)",member.getMemberId(),member.getSex(),member.getMemberPassword(),member.getMemberName(),member.getMemberNumber(),member.getGrade().toString());
    }

여기서, update() 메소드는 DataAcessException이라는 예외를 발생한다.

public int update(String sql,@Nullable Object... args ) throws org.springframework.dao.DataAccessException

public abstract class DataAccessException extends NestedRuntimeException

 그러나 DataAccessException은 RuntimeException의 자손이기 때문에 RuntimeException 계열이라 rollback이 된다.

 

그러므로, 이는 이유가 아니다.

 

2. org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false

아쉽게도, 이부분은 아직 건드릴 영역이 아니라고 생각했다..

 

3. springboot test 문서

아래는 springboot 문서의 번역이다.

 

“프록시 모드(기본값)에서는 프록시를 통해 들어오는 외부 메서드 호출만 가로챕니다. 즉, 자체 호출은 실제로 대상 개체의 다른 메서드를 호출하는 대상 개체 내의 메서드가 호출된 메서드가 @Transactional로 표시되더라도 런타임에 실제 트랜잭션으로 이어지지 않음을 의미합니다. 또한 예상되는 동작을 제공하려면 프록시를 완전히 초기화해야 하므로 초기화 코드(예: @PostConstruct)에서 이 기능에 의존해서는 안 됩니다.”

 

외부 메서드때문에 일어나는 일 같은데 아직 개념이 명확하게 잡히지 않아 어떻게 해결해야 하는 지 이해할 수 없었다.

 

따라서, AOP와 Transaction에대하여 공부를 하고 다시 이 문제의 원인을 분석하여 보겠다.

 

일단, 현재 본인이 원하는 상황은 test코드를 실행한뒤에 DB에 데이터를 삭제하는 것이므로

아래 코드를 이용하여 대체하겠습니다.

@AfterEach
void clear(){
    memberRepository.deleteAll();
}

 

 

이상입니다!!

 

원인 알려주시면 감사하겠습니다~~ㅠㅠ

728x90