프로젝트를 진행하던 도중 레코드를 업데이트 해야하는 상황이 있었다.
ORM 기술로 JPA를 사용하고 있었기에 잘 알려진 변경감지를 통해 데이터를 수정하려 했었다.
하지만 구현한대로 동작하지 않았고 수정 쿼리가 나가지 않는 문제를 마주했다.
문제가 없다고 생각했었기 때문에 다시 천천히 생각해보았다.
이전에 영속성 컨텍스트에 객체를 가져오지 않은 채 객체를 변경하여 원하는 대로 변경감지가 적용되지 않는 문제가 있었는데 이 경우는 다른 문제였다.
그렇기에 객체를 수정하는 로직이 아닌 해당 함수 내의 다른 쿼리들로 인한 문제일 가능성이 높다고 생각했다.
그 해결과정을 담아보았다.
간단히 설명하자면 회원 탈퇴 비즈니스 로직으로 회원을 soft delete하고 추가적으로 해당 회원이 생성한 컨텐츠들을 전부 삭제처리하는 로직이다.
회원 탈퇴 함수
@Transactional
override fun withDraw(user: User, request: RevokeRequest) {
if (user.authPlatform != request.authPlatform) {
throw ClientValidationException(AuthErrorCode.INCORRECT_PLATFORM)
}
userPort.delete(user)
contentPort.deleteByUserId(user.id)
}
Port를 통해 delete하는 코드만 보면된다.
content를 삭제하는 쿼리는 다음과 같이 구현되어 있다.
컨텐츠 삭제 쿼리
@Modifying(clearAutomatically = true)
@Query("""
update ContentEntity c set c.deleted = true
where c.categoryId in
(select ct.id from CategoryEntity ct where ct.userId = :userId)
""")
fun deleteByUserId(@Param("userId") userId: Long)
결국 문제는 이 벌크연산 쿼리였다.
벌크연산은 영속성 컨텍스트를 무시하고 DB에 직접 쿼리를 날린다.(트랜잭션 커밋 상관없이)!
그렇기 때문에 수정 이후에 데이터를 조회하면 DB와 영속성 컨텍스트의 데이터 불일치가 발생할 수 있어서 clearAutomatically
옵션을 통해 영속성 컨텍스트를 clear 해준다.
여기서 문제가 발생한다‼️
그래서 코드의 순서를 바꾸어 해결했다.
@Transactional
override fun withDraw(user: User, request: RevokeRequest) {
if (user.authPlatform != request.authPlatform) {
throw ClientValidationException(AuthErrorCode.INCORRECT_PLATFORM)
}
contentPort.deleteByUserId(user.id)
userPort.delete(user)
}
숲을 봐야하는데 나무만 봐서 간단한 문제로 시간을 꽤 쏟을 것 같다..
평소 JPA를 잘 이해하고 있다고 생각했고 그렇기에 이번 트러블도 당황스러웠다.
내가 아는 범주 안에선 문제가 없는데 의도한대로 동작하지 않아 더 당황한 것 같다.
각각의 개념은 잘 알더라도 실제 프로젝트를 구현하면서 이들을 모두 연관지어 잘 생각하는 것은 부족했다.
그래도 또 하나의 문제상황을 경험해서 좋고 해결해서 더 좋다!
앞으로는 생각을 좀 더 유연하게 하는 것에 익숙해져야겠다.