JPA로 em.remove()를 하였지만 delete 쿼리가 나가지 않는 에러가 발생하여 이를 해결하는 과정을 포스팅하려고 합니다.
1. delete 쿼리 안나가는 이슈 상황
1. test 코드입니다. 코드는 간단하게 아래와 같습니다.
package dev.devpool.service;
import dev.devpool.domain.Child;
import dev.devpool.domain.Member;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
@SpringBootTest
@Transactional
public class MemberServiceTest {
@Autowired
MemberService memberService;
@Autowired
EntityManager em;
@Test
public void ss() {
Member member = new Member();
member.setName("kim");
member.setEmail("rere25@naver.com");
member.setPassword("dasda");
member.setNickName("귀요미");
Child child = new Child();
child.setMember(member);
member.getChildren().add(child);
memberService.join(member);
System.out.println("================");
memberService.delete(member.getId());
System.out.println("================");
}
}
2. Transactional 어노테이션이 @Service 계층, Test 코드 두 개 모두에 있습니다.
물론 em.flush()를 통하면 delete 쿼리는 나갑니다. 하지만 @Transactional 어노테이션이 있는 경우 하나의 Transaction안에서 commit() 등을 모두 관리하기 때문에 delete flush()가 없더라도 delete 쿼리가 나가야된다고 생각했습니다.
2. delete 쿼리 안나가는 이슈 해결
결론적으로 Test 코드에서 @Transactional을 사용하면 큰 문제가 발생할 수 있습니다. 따라서 @AfterEach 메서드 등을 사용해 delete을 따로 해주는 것이 좋습니다. 사용하지 말아야하는 것은 아니지만 특히 Service 계층을 Test할 때는 사용 시 주의할 점들이 많습니다.
** 추가 @Transactional을 사용하지 않는다면 Test 코드에서 EntityManager를 사용하지 못해 flush() 등을 사용할 수 없습니다. 그렇다면 어떤 방법으로 쿼리를 확인하고 Test를 할 수 있을까요?
스프링이 제공하는 TransactionTemplate를 사용하면 됩니다.
수정한 코드는 아래와 같습니다.
@SpringBootTest
public class MemberServiceTest {
@Test
public void ss() {
transactionTemplate.execute(status -> {
Member member = new Member();
member.setName("kim");
member.setEmail("rere25@naver.com");
member.setPassword("dasda");
member.setNickName("귀요미");
Child child = new Child();
child.setMember(member);
member.getChildren().add(child);
memberService.join(member);
em.flush();
em.clear();
System.out.println("================");
memberService.delete(member.getId());
System.out.println("================");
return null;
});
}
}
3. delete 쿼리 안나가는 이슈 원인
Test 코드에서 데이터를 롤백시키기 위해 @Transactional을 붙이는 경우 몇가지 유의사항이 있습니다.
1. 메서드 단위로 트랜잭션이 적용되고 Commit()이 테스트 메서드 종료 시점에 수행되기 때문에 발생하는 이슈
Test 코드에서 @Transactional을 붙이면 메서드 단위로 트랜잭션이 적용됩니다. 테스트 코드가 있는 클래스에 @Transactional이 붙어있다면 마지막의 MemberService의 delete()에서 만들어져 쓰기지연 SQL 저장소에 적재된 delete 쿼리는 테스트에서의 @Transactional 때문에 날아가지 않습니다. @SpringBootTest를 붙인 통합 테스트에서 @Transactional을 붙이면 테스트를 종료하면서 테스트에서 했던 사항을 다 롤백합니다. 이 기능 + JPA 쓰기 지연 SQL 저장소가 더해져서 위와 같이 delete 쿼리가 나가지 않는 등의 이슈가 발생합니다.
2. LazyInitailizationException 이슈
서비스 계층에서 실수로 @Transactional을 붙여주지 않으면 Test코드에서는 @Transactional이 있어 영속성 컨텍스트를 통해 LazyLoading이 동작하지만 실제 운영시에 Lay Loading이 불가능해 LazyInitailizationException 에러가 발생할 수 있습니다. 물론 @DataJpaTest에는 @Transactional 어노테이션이 붙어있어서 의문이 들 수 있습니다. 하지만 이는 서비스 계층을 위한 테스트 어노테이션이 아닌 단순히 JPA 구성요소를 위한 Test임을 명심해야 합니다.
Spring @DataJpaTest docs 일부
Annotation for a JPA test that focuses only on JPA components.
'IT > 디버깅' 카테고리의 다른 글
[디버깅] bitbucket permission denied 오류 해결 (0) | 2023.04.26 |
---|---|
[디버깅] jdbSqlSyntaxErrorException Table not found 이슈 해결 (0) | 2023.04.05 |
[디버깅] JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation 에러 해결 (0) | 2023.03.30 |
[디버깅] Database may be already in use 에러 해결 (0) | 2023.03.26 |
[디버깅] SpringRunner.class 인식 오류 해결하기 (0) | 2023.03.16 |