JPQL 실행 시 Flush와 영속성 컨텍스트 동작 확인
JPQL 실행 시 Flush와 영속성 컨텍스트 동작 확인
이 테스트는 두 가지 주요 목적을 가지고 있습니다:
- JPQL 실행 전
flush
가 실행되는지 확인 - 영속성 컨텍스트에 동일한 ID를 가진 엔티티가 존재할 경우, JPQL로 가져온 값이 영속성 컨텍스트의 값으로 대체되지 않는지 확인
- JPQL 실행 전
@Test
@DisplayName("닉네임 변경 요청시 해당 회원의 닉네임을 변경한다.")
void updateMemberWithNickNameRequest() throws Exception {
//given
Member member = saveMemberAndMemberImage();
Long memberId = member.getId();
Jwt jwtCreatedBySavedMember = generateTokenWithMemberId(memberId);
MemberUpdateRequest memberUpdateRequest = MemberUpdateRequest.builder()
.nickname(CHANGED_NICK_NAME)
.build();
String jsonRequest = objectMapper.writeValueAsString(memberUpdateRequest);
//when & then
mockMvc.perform(
MockMvcRequestBuilders.patch("/api/members/{memberId}", memberId)
.header(AUTHORIZATION_STRING, JWT_TOKEN_PREFIX + jwtCreatedBySavedMember.getAccessToken())
.contentType(MediaType.APPLICATION_JSON)
.content(jsonRequest))
.andExpect(status().isOk());
Member updatedMember = memberProvider.findById(memberId);
assertThat(updatedMember.getNickname()).isEqualTo(CHANGED_NICK_NAME);
}
테스트 설명
Member 저장
Member member = saveMemberAndMemberImage(); Long memberId = member.getId();
이 메서드에 의해
member
와image
가 저장됩니다.@GeneratedValue(strategy = GenerationType.IDENTITY)
로 설정되어 있기 때문에member
가 저장될 때 바로 저장 쿼리가 실행됩니다. 이 상태에서 영속성 컨텍스트와 데이터베이스 모두에member
가 존재합니다. 이때Member
엔티티의isWithdrawal
필드는 데이터베이스에 의해 기본값으로 초기화되지만, 해당필드의 초기화 상태가 영속성 컨텍스트에는 반영되지 않습니다.
MockMvc 요청
mockMvc.perform( MockMvcRequestBuilders.patch("/api/members/{memberId}", memberId) .header(AUTHORIZATION_STRING, JWT_TOKEN_PREFIX + jwtCreatedBySavedMember.getAccessToken()) .contentType(MediaType.APPLICATION_JSON) .content(jsonRequest)) .andExpect(status().isOk());
이 요청은
member
의 닉네임을 변경합니다.updateMember
메서드는 JPQL을 통해member
를 조회합니다. 이때 JPQLmemberQueryService.findById(memberId)
이 실행되기 전에flush
가 자동으로 실행됩니다.@Transactional public void updateMember(Long memberIdFromJwt, Long memberId, MemberUpdateRequest memberUpdateRequest) { validateMemberId(memberIdFromJwt, memberId); Member member = memberQueryService.findById(memberId); // JPQL 실행 전 flush() 호출 Optional.ofNullable(memberUpdateRequest.getAlert()) .ifPresent(member::updateAlert); Optional.ofNullable(memberUpdateRequest.getNickname()) .ifPresent(member::updateNickname); }
entityManager.clear()
를 호출하지 않았으므로,entityManager
에는 여전히saveMemberAndMemberImage()
메서드에 의해 저장된member
가 존재합니다. 이 상태에서 JPQL로 가져온member
는 영속성 컨텍스트에 이미 존재하는member
로 대체되지 않습니다.(JPQL 을 통해 값을 가져오더라도, 영속성 컨텍스트안에 같은
ID
를 가진 객체가 있다면 JPQL 을 통해 가져온 값을 버리고 영속성 컨텍스트 안에 값을 유지하기 때문에 DB에 의해 기본값으로 초기화된 엔티티가 현재 영속성 컨텍스트 안의 엔티티로 대체되지 않습니다.)
Update 와 Flush
Member updatedMember = memberProvider.findById(memberId); assertThat(updatedMember.getNickname()).isEqualTo(CHANGED_NICK_NAME);
이 코드에서
memberProvider.findById(memberId)
는 JPQL 쿼리를 실행하며, 쿼리 실행 전에flush
가 호출됩니다.flush
가 호출될때,mockMvc.perform()
에 의해 실행된Member
엔티티 업데이트 쿼리가 나갈것입니다. 트랜잭션의 전파에 의해 test 코드에 걸어놓은 트랜잭션이 끝나지 않았으므로mockMvc.perform
에 의해 실행된 update 쿼리는mockMvc.perform
이 실행이 완료된 후에도 아직 실행되지 않기 때문입니다.
❗️이때 중요한것은 우린 아직 entityManager.clear()
를 한번도 호출하지 않았다는 점입니다. Member
엔티티 안에는 DB의 기본값에 의해 초기화되는 isWithdrawal
이라는 필드가 있는데, 아직 영속성 컨텍스트 안에 있는 entity
는 clear()
가 된 적이 없고, 따라서 DB에 의해 초기화된 필드는 영속성 컨텍스트 안에 적용이 되지 않았을 것입니다.
이를 검증하기 위해 아래와 같이 최종적으로 찾은 updatedMember
의 isWithdrawal
필드를 가져와보면 null
인것을 확인할수 있으며
만약 isWithdrawal
이 초기화된 상태를 보고싶다면 movkMvc.perform()
메서드 호출 이전 또는 이후에 entityManager
의 clear
메서드를 실행해주면 잘초기화된 isWithdrawal
값을 볼수있습니다.
참고로 mockMvc.perform
이후에 clear()
를 호출하는 로직에서 entityManger.flush()
가 추가된 이유는 만약 entityManger.clear()
만 호출시 mockMvc.perform()
에 의해 업데이트된 member
엔티티의 변경사항이 모두 detach
되면서 업데이트 쿼리가 나가지 않습니다. 따라서 mockMvc.perform
이후에는 entityManger.flush()
를 추가해 member
엔티티의 update
정보가 사라지지 않도록 합니다.
결론
JPQL이 실행되기 전에
flush
가 실행되는지 확인:mockMvc.perform()
이후,JPQL
로 인해update
쿼리가 실행되는 것을 확인했습니다. 이는JPQL
이 실행되기 전에flush
가 자동으로 호출된다는 것을 의미합니다.
영속성 컨텍스트에 동일한 ID의 엔티티가 존재할 때 JPQL로 가져온 값이 대체되지 않는지 확인:
entityManager.clear()
를 호출하지 않으면,JPQL
로 가져온 값이 영속성 컨텍스트에 이미 존재하는 값으로 대체되지 않는다는 것을 확인했습니다. 예를 들어,member.getIsWithdrawal()
이null
인 것을 확인했습니다.
사실, 엔티티 클래스 안에 초기화 로직을 넣거나 다른 방법으로 영속성 컨텍스트와 데이터베이스 간의 불일치를 줄이는 것이 가장 좋은 방법이지만, 이번 테스트는 이 불일치를 확인하기 위해 수행되었습니다.