ecsimsw

JPA / 데이터 수정 / 변경 감지와 병합 본문

JPA / 데이터 수정 / 변경 감지와 병합

JinHwan Kim 2020. 7. 18. 22:09

변경 감지와 병합

 

 JPA는 영속성 context에 테이블 형태의 1차 캐시를 두고 엔티티를 저장해서,

 

 Transaction commit 시, 1차 캐시의 영속된 값과 현재 entity 값을 비교하여 변경이 된 것을 알아서 적용해준다.

 

 

준영속 상태의 엔티티

 

  아래 코드를 가볍게 보면, PostMapping으로 "members/memberId/editName" 요청을 받아

  

  파라미터로 넘겨 받은 MemberNameEditForm의 form데이터로 item 값을 넣어줘서 

 

  form의 id에 해당하는 member의 Name을 set했다.

 

  이후에 transaction이 commit되면, 알아서 변경 감지가 일어날 것이라고 생각한 잘못된 코드이다.

// 준영속 상태를 생각하지 않은 엔티티 값 수정

@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";

  이미 한번 DB에 저장된 기존 식별자(id)를 갖는 엔티티는 영속성 컨텍스트가 더 이상 관여하지 않는다.

 

  이런 상태의 Entity를 준영속 상태의 엔티티라고 한다.

 

  위 코드에서 member 역시, 기존에 DB에 저장된 id를 식별자로 갖는 준영속 상태의 엔티티이기 때문에

 

  영속성 컨텍스트에 의해 관리되지 않고, 때문에 변경 감지가 적용되지 않는 것이다.

 

 

준영속 상태의 엔티티를 수정하는 2가지 방법

 

1. 변경 감지 기능(dirty checking) 사용.

2. merge 사용

 

1번 dirtyChecking 사용

@Transactional

void updateMember(Member memberParam)
Member findMember = em.find(Member.class, memberParam.getId());

findMember.setName(memberParam.getName());

 

  entityManager로 entity를 직접 꺼내, 값을 수정한다.

 

  이렇게 하면 dirtyChecking이 일어난다.

 


2번 merge 사용

@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";

  entityManager의 merge를 사용할 수 도 있다.

 

  merge가 호출되면 우선 해당 엔티티를 1차 캐시에서 먼저 조회하고, 

 

  없다면 식별자로 DB에서 엔티티을 검색해 가져와 준영속 상태의 엔티티 값을 대입 받는다.

 

 

  아래처럼 Service에서 Member를 저장할 때, id가 직접 설정하지 않았으면(새로 생성) persist를,

 

  id를 직접 설정한 객체가 저장된다면 이미 있는 엔티티를 수정하는 것으로 알고 merge를 호출하는 식으로 사용된다.

public void save(Member member){

  if(Member.getId() == null){
     em.persist(member);
  } 
  
  else{ 
     em.merge(member);
  }
}

  
merge 사용시 주의1)

 

  위 예시에서 준영속 상태의 member 자체를 영속성 컨텍스트에 넣는 것이 아니다.

 

  merge는 준영속 객체를 영속하는 것이 아니라,

 

  준영속 객체의 식별자와 일치하는 엔티티를 db에서 가져와 값을 대입 받고, 해당 그 엔티티를 반환하는 것이다.

 

  따라서 위 코드에서 member는 준영속 상태 그대로이고,

 

  만약 db에서 가져온 영속된 엔티티를 받고 싶다면 merge의 리턴 값을 받아서 사용해야 한다. 

Member realMember = em.merge(member);

  

 

merge 사용시 주의 2)

 

  merge는 엔티티 모든 필드를 그대로 변경한다.

 

  만약 member의 name만 변경하고자 member만 세팅하고 merge한다면 

 

  member의 나머지 필드는 기존의 값을 잃고 null이 대입된다.

 

 

결론

 

  가급적 em.find로 직접 엔티티를 가져와서 엔티티 값을 수정하는 것이 좋다.

Comments