JPA Auditing 사용 시 주의사항
이전 글에서 벌크 업데이트 시 JPA Auditing이 동작하지 않는다는 점을 배웠습니다.
그런데 아래 테스트 코드를 보면, 벌크 업데이트 후에 updatedAt 필드가 자동 갱신되는 것처럼 보입니다.
채팅 요청에 대해 벌크 연산을 진행했을 경우, updatedAt 필드도 함께 갱신되는지 확인하는 테스트 코드입니다.
@DisplayName("벌크 함수 호출 시, updated_at 필드가 같이 변경된다.")
@Test
void changeUpdatedAtWhenChangeStatusOfChatInquiry() {
// given
ChatInquiry chatInquiry = chatInquiryRepository.save(ChatInquiryFixture.chatInquiry(questionPost, questioner, answerer, chatMessage));
ReflectionTestUtils.setField(chatInquiry, "createdAt", LocalDateTime.now().minusWeeks(1));
ReflectionTestUtils.setField(chatInquiry, "updatedAt", LocalDateTime.now().minusWeeks(1));
log.info("chatInquiry.updatedAt = {} " , chatInquiry.getUpdatedAt().toString());
//when
chatInquiryRepository.updateChatInquiryStatusRejected();
em.clear();
//then
ChatInquiry foundChatInquiry = chatInquiryRepository.findById(chatInquiry.getId()).orElseThrow();
log.info("foundChatInquiry.updatedAt = {} " , foundChatInquiry.getUpdatedAt().toString());
};
일주일이 지난 채팅 요청을 변경하기 때문에 임의로 createdAt과 updatedAt을 일주일 전으로 조정하였습니다.
이때 member 변수의 접근 제어자가 private이기 때문에 ReflectionTestUtils을 사용하였습니다.
테스트 후 로그를 확인해 보면, 벌크 업데이트 함수 실행 전후로 updatedAt 시간이 다른 것을 확인할 수 있습니다.
이는 벌크 업데이트 함수 호출 시 JPA Auditing이 작동한 것처럼 보입니다.
벌크 업데이트에서는 JPA Auditing이 동작하지 않는데 왜 이런 결과가 나오는 걸까요?
ReflectionTestUtils로 설정한 updatedAt 필드는 DB에 쿼리가 전송되는 순간
JPA Auditing에 의해 현재 시간으로 덮어쓰게 됩니다.
2025-01-12T14:27:52.068+09:00 INFO 1441 --- [ Test worker] p6spy : #1736659672068 | took 3ms | statement | connection 2| url jdbc:mysql://localhost:49632/test
update chat_inquiry set answerer_id=?,created_at=?,inquirer_id=?,message=?,question_post_id=?,status=?,updated_at=? where chat_inquiry_id=?
update chat_inquiry set answerer_id=2,created_at='2025-01-05T14:27:51.930+0900',inquirer_id=1,message='와우',question_post_id=1,status='PENDING',updated_at='2025-01-12T14:27:52.049+0900' where chat_inquiry_id=1;
결론적으로 DB의 updatedAt 필드를 특정 시간으로 설정해도
JPA Auditing을 사용하는한 쿼리 전송 순간으로 업데이트됩니다.
bulk update전 auto flush 발생
위 테스트 코드의 실행 시 변경 감지 로그가 bulk update 로그 이전에 나오는 것을 알 수 있습니다.
update
chat_inquiry
set
answerer_id=?,
created_at=?,
inquirer_id=?,
message=?,
question_post_id=?,
status=?,
updated_at=?
where
chat_inquiry_id=?
Hibernate:
update
chat_inquiry
set
status=?
where
created_at<=?
and status=?
bulk update 전에 실제로 auto flush를 해주는지 확인해 보고자 아래 테스트코드를 작성해 보았습니다.
맨 아래 bulk update 유무에 따라 sql 로그가 어떻게 뜨는지 확인해 보았습니다.
@DisplayName("jpa로 벌크 업데이트 시 영속성 컨텍스트 1차 캐시에 있던 쿼리들이 DB로 flush된다.")
@Test
void test() {
Member member = memberRepository.findById(1L).orElseThrow();
member.setMajor(Major.ISE);
memberRepository.bulkUpdate_querydsl(Status.B);
}
bulk update문이 있는 경우 멤버 업데이트 로그 다음에 벌크 연산 로그가 나옵니다.
Hibernate:
update
member
set
created_at=?,
major=?,
nickname=?,
status=?,
updated_at=?
where
member_id=?
Hibernate:
update
member m1_0
set
status=?
맨 아래 코드를 주석처리했을 때에는 멤버 업데이트 로그가 보이지 않습니다.
@DisplayName("jpa로 벌크 업데이트 시 영속성 컨텍스트 1차 캐시에 있던 쿼리들이 DB로 flush된다.")
@Test
void test() {
Member member = memberRepository.findById(1L).orElseThrow();
member.setMajor(Major.ISE);
//memberRepository.bulkUpdate_querydsl(Status.B);
}
테스트 @Transactional이 없기 때문에 실제로 업데이트 쿼리가 나가려면 수동으로 flush 해줘야 합니다.
결론적으로 bulk update 실행 전 auto flush가 됨을 확인하였습니다.
느낀 점
JPQL 쿼리를 실행 전에 JPA가 auto flush로 쓰기 지연 SQL 저장소 쿼리를 DB에 날리는 것은 알았습니다.
그런데 QueryDsl로 작성한 bulk update문을 실행하기 전에도 auto flush가 되는지는 몰랐습니다.
테스트로 SQL 로그를 찍어서 확인해 보며 영속성 컨텍스트 및 트랜잭션 개념을 다시 생각해 볼 수 있었습니다.