지난번에 진행했던 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
이 대입되는 위험한 상황이 발생한다!!
이를 해결할 가장 좋은 방법은 엔티티를 변경할 때는 항상 변경 감지를 사용하는 것이다.
📌 참고 자료
좋은 글 감사합니다ㅎ.ㅎ