
가끔 이런 코드가 있다.
Scenario #
1. 테이블의 특정옵션 초기화
2. 특정 row의 특정옵션 설정
나의 경우 프로젝트에서 기본배송지 설정과 관련한 로직을 구현하다가 이런 시나리오가 나왔다.
Usecase #
1. "기본배송지 설정" 클릭 시
2. 클릭한 회원과 관련된 모든 배송지 false (true가 기본배송지)
3. 기본 배송지로 선택한 배송지 수정된 내용과 배송지 옵션 true로 변환
코드의 구현은 아래와 같이 진행했다.
//Usecase 1.
@Transactional
public void updateMemberDelivery(Long memberDeliveryId, MemberDeliveryRequest request) {
var memberDelivery = memberDeliveryRepository.findById(memberDeliveryId)
.orElseThrow(() -> new CustomException("배송지가 없습니다."));
if (request.getIsDefault()) {
Member member = getMemberEntity();
//Usecase 2.
memberDeliveryRepository.updateMemberDeliveryDefault(member.getId(), memberDelivery.getId());
}
//Usecase 3.
memberDelivery.updateMemberDelivery(request);
}
해당 코드의 updateMemberDeliveryDefault는
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query(
"UPDATE 회원배송지 md " +
"SET md.기본배송지 = "
+ "CASE "
+ "WHEN md.배송지 pk = :memberDeliveryId "
+ "THEN true "
+ "ELSE false END " +
"WHERE md.회원 id = :memberId"
)
void updateMemberDeliveryDefault(@Param("memberId") Long memberId, @Param("memberDeliveryId") Long memberDeliveryId);
그리고 updateMemberDelivery는 jpa의 더티체킹을 이용한 영속화 메서드이다.
/**
* 회원 배송지 수정
* @param request MemberDeliveryRequest
*/
public void updateMemberDelivery(MemberDeliveryRequest request) {
this.title = request.getTitle();
this.name = request.getName();
this.phone = request.getPhone();
this.address = request.getAddress();
this.addressDetail = request.getAddressDetail();
this.location = request.getLocation();
this.content = request.getContent();
this.isDefault = request.getIsDefault();
}
내 생각대로 흘러간다면 query log는
query log
1. 회원배송지 select
2. 회원 select
3. 회원배송지 전부 false update
4. 선택한 회원배송지 true update
위와 같은 흐름으로 나와야 한다고 봤다. 하지만 쿼리는
[Hibernate]
select
회원 배송지 조회
from
회원 배송지 md1_0
where
md1_0.id=?
[Hibernate]
select
회원 조회
from
회원 m1_0
where
m1_0.id=?
[Hibernate]
update
회원 배송지
set
기본 배송지(true/false)=case
when id=?
then true
else false
end
where
회원 pk=?
결과는 이런 식으로 나왔다.
Usecase의 1,2번까지는 정상적으로 동작 후 3번째 회원의 배송지 정보를 수정하는 query가 나가지 않는것이다.
사실 코드를 만들때 부터 긴가민가한 의문점이 있었다.
var memberDelivery = memberDeliveryRepository.findById(memberDeliveryId)
"UPDATE 회원배송지 md " +
"SET md.기본배송지 = "
+ "CASE "
+ "WHEN md.배송지 pk = :memberDeliveryId "
+ "THEN true "
+ "ELSE false END " +
"WHERE md.회원 id = :memberId"
이 2개의 코드 즉, findById로 조회한 MemberDelivery는 영속성 컨텍스트에 저장하게 되고
@Query 어노테이션으로 update를 진행하는 쿼리는 영속성 컨텍스트 접근없이 직접 데이터베이스에 해당 쿼리를 동작시켜버린다.
그렇기때문에 이게 만약 동작하더라도 영속성 변화를 jpa가 눈치챌 수 있을까? 하는 생각이 있었다.
즉, jpa는 조회한 MemberDelivery 객체를 영속성 컨텍스트에 넣고 바라보고 있는데 내가 직접 jpa를 거치지 않고 @Query 메서드를 통해 DB의 값들을 바꿔버렸다면 jpa에서 보는 영속성 컨텍스트의 값들은 틀린 값들이 되기 때문에 정상적으로 동작하지 않을 것 이다. 라고 유추하였다.

2가지의 해결방안이 있다고 생각한다.
한방 update쿼리의 경우 조금 복잡해질 가능성이 있다고 생각해 1번의 방법을 사용해봤다.
//기존 코드
@Transactional
public void updateMemberDelivery(Long memberDeliveryId, MemberDeliveryRequest request) {
var memberDelivery = memberDeliveryRepository.findById(memberDeliveryId)
.orElseThrow(() -> new CustomException("배송지가 없습니다."));
if (request.getIsDefault()) {
Member member = getMemberEntity();
memberDeliveryRepository.updateMemberDeliveryDefault(member.getId(), memberDelivery.getId());
}
memberDelivery.updateMemberDelivery(request);
}
//변경한 코드
@Transactional
public void updateMemberDelivery(Long memberDeliveryId, MemberDeliveryRequest request) {
var memberDelivery = memberDeliveryRepository.findById(memberDeliveryId)
.orElseThrow(() -> new CustomException("배송지가 없습니다."));
//순서만 바꿨다.
memberDelivery.updateMemberDelivery(request);
if (request.getIsDefault()) {
Member member = getMemberEntity();
memberDeliveryRepository.updateMemberDeliveryDefault(member.getId(), memberDeliveryId);
}
}
실행 query log
[Hibernate]
select
회원 배송지 조회
from
회원 배송지 md1_0
where
md1_0.id=?
[Hibernate]
select
회원 조회
from
회원 m1_0
where
m1_0.id=?
[Hibernate]
update
회원 배송지
set
배송지 제목=?,
수정날짜=?
where
id=?
[Hibernate]
update
회원 배송지
set
기본 배송지(true/false)=case
when id=?
then true
else false
end
where
회원 pk=?
내 생각대로 더티체킹으로 인한 배송지update 쿼리 이후 나머지 기존 배송지의 배송지 상태값을 전부 변경하는 update 쿼리가 동작하게 되었다.
내가 해결한 이 상황이 jpa를 완벽하게 이해하고 해결한 것이 아니다.
이건 엄연히 나의 추측을 기반으로 하여 해결한 상황이다. (올바른 해결방안이 아닐 수도 있다.)