@Test
public void 계좌삭제_test() throws Exception {
// given
Long number = 1111L;
Long userId = 2L;
// stub 1
User ssar = newMockUser(1L, "ssar", "쌀");
Account ssarAccount = newMockAccount(1L, 1111L, 1000L, ssar);
when(accountRepository.findByNumber(any())).thenReturn(Optional.of(ssarAccount));
// when
accountService.계좌삭제(number, userId);
// then
assertThrows(CustomApiException.class, () -> accountService.계좌삭제(number, userId));
}
@BeforeEach
public void setUp() {
User ssar = userRepository.save(newUser("ssar", "쌀"));
User cos = userRepository.save(newUser("cos", "코스"));
Account ssarAccount1 = accountRepository.save(newAccount(1111L, ssar));
Account cosAccount1 = accountRepository.save(newAccount(2222L, cos));
}
.
.
.
@WithUserDetails(value = "ssar", setupBefore = TestExecutionEvent.TEST_EXECUTION)
@Test
public void deleteAccount_test() throws Exception {
// given
Long number = 2222L;
// when
ResultActions resultActions = mvc.perform(delete("/api/s/account/" + number));
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : " + responseBody);
// then
}
쿼리를 보자
2개의 insert, ssar cos
account 2개 insert
1번쨰 select는 @WithUserDetail 때문에 발생
2번쨰 account를 select
account 서비스를 가보면
쿼리 비교
Account의 user는 LAZY이기 때문에 select를 할 때 가져오지 않는다.
이 부분에서 user.getId()가 있지만 LAZY를 하지 않았다. 왜 그런걸까??
LAZY여도 값들은 전부 DB에서 가져온다. 단지 user의 id 값만 가져오는 것이지, username, password, email, fullname은 가져오지 않는다.
즉, 다시 말하면 id 값만 조회를 할 때는 LAZY로딩이 발동하지 않고, 그 외의 username, password, email, fullname을 조회할 때는 LAZY 로딩이 발동하는 것이다.
근데 쿼리를 보면
처음 UserDetail에서 ssar을 select 했기 때문에 PC에 ssar 존재
ssarAccount 도 PC에 존재
LAZY 로딩이 발동해도 쿼리를 볼 수 없는 이유는 ssar이 이미 PC에 존재하기 때문이다.
근데 테스트를 할 때는 PC에 커밋된 상태를 전부 비워줘야 한다. 이유는 직접 쿼리를 확인을 해야하기 때문에
따라서 처음 setUp()에 ssar, cos가 PC에 존재하지 않게 clear()를 해주자
@ActiveProfiles("test") // 테스트 모드
@Transactional
@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
public class AccountControllerTest extends DummyObject {
@Autowired
private MockMvc mvc;
@Autowired
private ObjectMapper om;
@Autowired
private EntityManager em;
@Autowired
private UserRepository userRepository;
@Autowired
private AccountRepository accountRepository;
@BeforeEach
public void setUp() {
User ssar = userRepository.save(newUser("ssar", "쌀"));
User cos = userRepository.save(newUser("cos", "코스"));
Account ssarAccount1 = accountRepository.save(newAccount(1111L, ssar));
Account cosAccount1 = accountRepository.save(newAccount(2222L, cos));
em.clear();
}
.
.
.
/**
* 테스트시에는 insert 한것들이 전부 PC에 올라감(영속화)
* 영속화 된것들을 초기화 해주는 것이 개발 모드와 동일한 환경으로 태스트를 할 수 있게 해준다.
* 최초 SELECT는 쿼리가 발생하지만! PC에 있으면 1차 캐시
* LAZY 로딩은 쿼리도 발생함 - PC에 있다면
* LAZY 로딩할 때, PC 없다면 쿼리가 발생함
*/
@WithUserDetails(value = "cos", setupBefore = TestExecutionEvent.TEST_EXECUTION)
@Test
public void deleteAccount_test() throws Exception {
// given
Long number = 1111L;
// when
ResultActions resultActions = mvc.perform(delete("/api/s/account/" + number));
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : " + responseBody);
// then
}
쿼리확인
ssar, cos insert
ssarAccout, cosAccount insert
em.clear -> PC 비워진 상태
인증을 cos -> cos SELECT 쿼리
accountRepository.findByNumber()
checkOnwer() -> user.getUsername();, lazy loading
테스트 시, insert 된것들을 PC 에서 비우자!!
@WithUserDetails(value = "cos", setupBefore = TestExecutionEvent.TEST_EXECUTION)
@Test
public void deleteAccount_test() throws Exception {
// given
Long number = 1111L;
// when
ResultActions resultActions = mvc.perform(delete("/api/s/account/" + number));
String responseBody = resultActions.andReturn().getResponse().getContentAsString();
System.out.println("테스트 : " + responseBody);
// then, JUnit 테스트에서 delete 쿼리는 DB관련으로 가장 마지막에 실행되면 발동안함.
assertThrows(CustomApiException.class, () -> accountRepository.findByNumber(number).orElseThrow(
() -> new CustomApiException("계좌를 찾을 수 없습니다. ")));
}
마지막 findByNumber 떄문에 쿼리 발생
JOIN FETCH
SELECT ac FROM Account ac JOIN FETCH ac.user u WHERE ac.number = :number
만약 Account를 가져올 때 계속해서 user를 가져오면 매우 비효율적이다. 따라서 JOIN FETCH를 사용해 미리 SELECT를 한다. 즉 LAZY 한 것들을 쿼리로 제어해서 미리 가져오는 것이다.