[JPA] 조회한 Entity 업데이트시, save()를 호출하지 않아야하는 이유
회사프로젝트 코드의 대부분은 다음과 같이 구현되어있습니다.
1. 조회 -> 2. 데이터 변경 -> 3. save() 호출
간단한 예시 - 1
// 1. 조회
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new NotFoundException("회원 찾기 실패", memberId));
// 2. 데이터 셋팅
member.setName("홍길동");
member.setAge(20);
// 3. save() 호출
memberRepository.save(member);
코드를 실행하면 문제없이 원하는 결과대로 동작합니다.
영속성 컨텍스트에 영속되는 Entity
조회해온 member 는 영속성 컨텍스트에서 관리하는 대상이 됩니다.
이를 "영속"되었다고 말합니다.
영속성 컨텍스트는 트랜젝션이 끝나기 전에 flush 하는데, flush 에서는 영속된 데이터들을 처음과 같은지 비교하는 작업을 거칩니다. 이 과정을 "더디체킹(Dirty Checking)"이라고 말합니다.
이 과정에서 변경된 entity는 update문이 DB로 commit 됩니다.
save(..) 해줄 필요 없다.
결과적으로 save() 해줄 필요는 없습니다. 그래도 save() 메소드를 호출하는 것과 동일하게 동작하기 때문에 호출 했다고 해서 결과에 영향을 끼치지는 않습니다.
에러도 안나고.. 그냥 직관적이게 save(..) 메소드를 호출해주면 안될까?
이 부분에 관해서는 꽤 오랜 기간 의심의 시간을 가졌었습니다. 사실 save()를 해주는 것과 안해주는 것의 차이가 없다고 보였기 때문입니다. 'save()를 써줌으로써 훨씬 더 직관적이지 않을까?' 하는 생각이 들기도 합니다.
하지만 경우에 따라 잘못 이해하고 사용하면 의도와 다르게 동작할 수 있습니다.
간단한 예시 - 2
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new NotFoundException("회원 찾기 실패", memberId));
member.setName("홍길동");
member.setAge(20);
if (member.getAge() > 10) {
memberRepository.save(member);
return;
}
// ...
JPA의 가장 중요한 특징인 '영속성 컨텍스트'의 원리를 모르는 개발자가 이 코드를 본다면, 10살 보다 많은 member만 update가 발생하고, 10살 이하의 member는 업데이트가 되지 않을 것으로 해석하게 됩니다. 의도와는 다르게 10살 이하의 member 가 update되기 때문에 큰 문제가 생길 위험이 있습니다.
save() 메소드 자체로는 결과에 영향을 끼치지는 않습니다. 하지만 코드를 읽을 때 의도와 다르게 해석될 수 있습니다. 따라서 영속성 컨텍스트의 동작 원리에 맞춰 불필요한 save() 는 호출하지 않아야 한다고 생각합니다.