JUnit으로 테스트 케이스를 만들어 실험을 하다가 h2 데이터베이스를 사용했을 때는 되던것이 로컬 MySQL서버와 연결하니 되지 않았다.
문제는 primary key가 reset이 되지 않고 @Transaciton에 대한 이해가 부족하여 많은 문제가 발생했기 때문이다.
이에 @Transactional에 대해 정리해보려고 한다.
다음 글은 두가지 질문에 답을 하고자 한다.
1. JUnit에서 @Before~과 @After의 트랜잭션 범위는?
2. JUnit에서 @Transaction에서는 롤백이 되는데 다른 곳에서는 @Transaction은 롤백이 되지 않을까?
트랜잭션
트랜잭션이란 데이터 베이스의 상태를 변화시키기 해서 수행하는 작업의 단위를 뜻한다. 작업의 단위는 단순히 SQL의 질의어 하나만이 아닌 사용자가 원하는 기준에 따라 정해진다.
특징
- 원자성 : 트랜잭션이 모두 반영되던가 아예 반영되지 않아야한다는 것
- 일관성 : 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 할것
- 독립성 : 둘 이상의 트랜잭션이 실행되고 있어도 각각의 트랜잭션은 서로에게 영향을 미칠 수 없다.
- 지속성 : 트랜잭션이 성공적으로 완료되었을 경우 결과는 영구적으로 반영되어야한다는
Commit과 Rollback
- Commit : 하나의 트랙잭션이 성공적으로 끝났고 데이터 베이스가 일관성 있는 상태일 때 하나의 트랜잭션이 끝났음을 알려주기 위한 연산
- Rollback : 트랙잭션 중 Exception이나 원자성이 깨진경우 트랜잭션을 처음부터 다시 시작하거나 트랜잭션의 부분적인 연산된 결과를 취소시키는 연산
@Transactional
Spring의 JPA에서 많이 사용하는 기술로 하나의 Transaction을 begin하고 commit까지 시켜주는 어노테이션이나. 더욱이 Exception 발생시 Rollback까지 시켜준다.
이를 위해 Transaction 어노테이션이 실행 되기 전에 transaction begin 코드를 삽입하여 메서드가 실행된 후 transaction commit코드를 삽입하여 객체 변경 감지를 수행하게 유도한다.
@Before~과 @After~의 Transaction 범위
이제 본 게임이다. 아래와 같은 상황에서 어떻게 Transaction이 설정될까?
@Before~
@Transactional
public void setUp() {
// Test를 위한 데이터 초기화
}
@After~
@Transactional
public void reset() {
// Test이후 데이터 삭제
}
@Test
@Transactional
public void test() {
// 테스트
}
정답은! 어떤 방식을 쓰느냐에 다르다.
메소드 단위의 lifecycle을 가지는 어노테이션의 경우(@BeforeEach, @AfterEach) @Test와 같은 Transaction을 공유한다.
반면에 클래스 단위의 lifecycle을 가지는 어노테이션의 경우(@BeforeTestClass, @AfterTestClass)는 @Test와 같은 Transaction을 공유하지 않는다.
Q) 왜 JUnit에서는 rollback에 대한 Exception을 설정하지 않아도 rollback이 진행될까?
우리가 테스트 케이스를 만들고 진행할 때
@Test
@Transactional
@DisplayName("Post API Test")
public void PostTest() throws Exception {
Map<String, String> request = new HashMap<>();
request.put("todo", "Hello World");
String json = new ObjectMapper().writeValueAsString(request);
mvc.perform(post(TEST_URL)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(json))
.andExpect(status().isOk())
.andExpect(jsonPath("id").exists())
.andExpect(jsonPath("todo").value(request.get("todo")));
}
다음과 같이 만들어 놓으면 롤백이 된다. 이상한 점은 내가 Test 내에서 Exception을 발생시키지 않는데 rollback이 된다는 것이다.
왜 그럴까?
답은 우리가 JUnit을 사용하기 때문이다. Spring Test의 공식 문서에서는
이와 같은 설명이 있다. 즉, 위의 어노테이션이 있다면 Transaction이 끝나고 commit을 하지 않고 rollback을 시킨다는 의미이다.
그리고 Spring Test에 TranscationTestExecutionListener에는 다음과 같이
testClass에 대해서 Rollback 어노테이션과 자동적으로 결합해주는 부분이 존재한다.
따라서 우리가 JUnit을 활용한 Test 상황에서 단지 Transaction이 있다는 이유로 Rollback이 되는 이유는
spring에서 자동적으로 이러한 Transaction에 대해 정의되어있는 메소드나 클래스를 TranscationTestExecutionListener에서 탐색하고 탐색이 되면 이를 Rollback class와 결합해주기 때문이다.
참고
https://stackoverflow.com/questions/17308335/before-and-transactional
긴 글 읽어주셔서 감사합니다.
틀린 부분이 있으면 댓글을 달아주시면 감사하겠습니다.
📧 : may3210@g.skku.edu
'개발 > Spring' 카테고리의 다른 글
[Spring] Spring Security의 이해 (0) | 2022.01.19 |
---|---|
[Spring] Spring 을 이용한 웹 서비스 구조 (0) | 2022.01.12 |
[Spring MVC] DispatcherServlet은 어떻게 request랑 controller를 이어줄까? (0) | 2022.01.06 |
[Spring JPA] ORM과 JPA (1) | 2022.01.05 |
[Spring] Controller request 유효성 검사 (0) | 2022.01.05 |