지난번에 진행했던 hello shop 프로젝트에서 상품 수정 시 사용됐던 merge() 라는 친구를 기억하나 혹시~😎?
JPA에서 준영속 엔티티를 수정하는 방법에는 2가지가 있는데, 이것이 매우 중요하다고 강조를 하셔서 다시 한 번 정리하는 글을 작성해보려고 한다!
고 고 고 ~
영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다.
임의로 만들어낸 엔티티라도 기존 식별자를 가지고 있는 경우(JPA가 식별할 수 있는 id를 가지고 있는 경우)에는 준영속 엔티티라고 말한다.
예를 들어, 내가 Food 라는 객체를 만들었다고 가정하자.
Food 객체는 이미 데이터베이스에 한 번 저장되어서 식별할 수 있는 id 가 존재한다.
이것 또한 기존 식별자를 가지고 있는 경우이므로 준영속 엔티티라고 볼 수 있다!
🤔 : 여기서 갑자기 준영속 엔티티가 왜 나왔냐~ 하면~~~?
JPA는 엔티티를 수정할 때 영속성 컨텍스트에 테이블 형태의 1차 캐시를 두고 엔티티를 저장해서,
Transaction commit 시에, 1차 캐시의 영속된 값과 현재 entity 값을 비교하여 변경이 된 것을 알아서 적용해준다!
// 준영속 상태를 생각하지 않은 엔티티 값 수정
@PostMapping("/members/{memberId}/editName")
public String updateMemberName(@ModelAttribute("form") MemberNameEditForm form){
Member member = new Member();
member.setId(form.getId());
member.setName(form.getName());
return "redirect:/members";
위의 코드는 PostMapping 으로 "members/memberId/editName" 요청을 받아
파라미터로 넘겨 받은 MemberNameEditForm의 form 데이터로 item 값을 넣어줘서
form 의 id 에 해당하는 member 의 Name 을 set 했다.
➡️ 트랜잭션이 커밋되어도, 변경감지가 일어나지 않는다.즉 잘못된 코드이다.
1. 변경 감지 기능 사용
2. 병함(merger) 사용
@Transactional
void updateMember(Member memberParam)
Member findMember = em.find(Member.class, memberParam.getId());
findMember.setName(memberParam.getName());
entityManager로 entity를 직접 꺼내, 값을 수정하는 방식이다.
영속성 컨텍스트에서 엔티티를 다시 조회한 후, 데이터를 수정한다.
트랜잭션 안에서 엔티티를 다시 조회하고, 변경할 값을 선택하는데,
트랜잭션 커밋 시점에 변경 감지(Dirty Checking) 이 동작한다.
@PostMapping("/members/{memberId}/editName")
public String updateMemberName(@ModelAttribute("form") MemberNameEditForm form){
Member member = new Member();
member.setId(form.getId());
member.setName(form.getName());
em.merge(member);
return "redirect:/members";
병합은 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용한다.

1. merge()를 실행
2. 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회
➡️ 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고, 1차 캐시에 저장
3. 조회한 영속 엔티티(mergeMember)에 member 엔티티의 값을 채워 넣음
➡️ member 엔티티의 모든 값을 mergeMember에 밀어 넣는데, 이 때 mergeMember의 “회원1”이라는 이름이 “회원명변경”으로 바뀜
4. 영속 상태인 mergeMember를 반환
✔️ 병합 시 동작 방식 정리
1️⃣ 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회
2️⃣ 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체 (=병합, 쉽게 말하면 바꿔치기!!!)
3️⃣ 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행
merge() 는 엔티티의 모든 필드를 그대로 변경한다.
만약 member 의 name 필드만 변경하고자 수정하고 merge() 한다면
member 의 나머지 필드는 기존의 값을 잃고 null 이 대입되는 위험한 상황이 발생한다!!
이를 해결할 가장 좋은 방법은 엔티티를 변경할 때는 항상 변경 감지를 사용하는 것이다.
📌 참고 자료
좋은 글 감사합니다ㅎ.ㅎ