jpa merge 와 dirty checking
준영속 객체의 update를 위하여
- 영속성 컨텍스트에서 관리를 하기 위해서는 특정 객체게 엔티티 매니저에 영속성으로 들어가도록 하거나(
em.persist(obj)
), 영속성 컨텍스트로 객체를 찾아오면 된다(em.find(Member.class, 1L)
). - 만약 DB에서의 아이디(
@Id
)의 값을 알고, 그 값을 객체로 생성하였고, 이를 update 하려면 어떻게 해야할까? - 여기서 논의할 방법은 두 가지이다. merge병함과 dirty checking 더티 체킹이다.
병합
// 1) 회원을 등록
Member member = new Member();
member.setName("kim");
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
long memberId = member.getId();
// 2) update를 위하여 객체를 생성하여 persist에 삽입
Member updateMember = new Member();
updateMember.setId(memberId);
updateMember.setAge(11);
try {
em.persist(updateMember);
}catch (PersistenceException e) {
e.printStackTrace();
System.out.println("========= detached entity passed to persist ==========");
}
// 3) merge를 통해 update를 진행. 영속화된 객체는 리턴으로 받는다.
Member resultMember = em.merge(updateMember);
em.flush();
em.clear();
System.out.println("============ merge를 통해 update에 성공 ==============");
System.out.printf("원래 값 : %s, %d\n", member.getName(), member.getAge());
System.out.printf("merger로 업데이트 한 값 : %s, %d\n", resultMember.getName(), resultMember.getAge());
em.flush();
em.clear();
- 2) update를 위하여 persist를 시도하면 예외가 나온다. @Id @GenerateValue 인 필드에 값이 있을 경우, 준영속(detached entity) 상태로 보기 때문이다.
-
준영속 상태란, 영속되었으나 영속성 컨텍스트에서 관리하지 않는 객체를 의미한다.
- 3)에서 merge를 통해 update를 성공했다.
- merge에 삽입한 객체가 아닌, 리턴한 객체를 사용해야 한다. 이 객체는 영속성 컨텍스트가 관리한다.
- 다만 merge는 문제를 가진다.
Hibernate:
/* update
jpa9_merge.Member */ update
Member
set
age=?,
name=?
where
id=?
============ merge를 통해 update에 성공 ==============
원래 값 : kim, 10
merger로 업데이트 한 값 : null, 11
- 위의 코드의 의도는 1년이 지나서 10살에서 11살로 업데이트하고 싶었다. 그래서 age만 set을 하였지만, 사실상 setName을 하지 않았으므로 그 값은 null 이 된다. null 역시 update 되어 DB에는 이름이 사라진다.
- 비지니스 로직에서 엔티티의 전체 데이터를 update 하는 경우가 사실은 없다. 보통은
public void addAge(){ this.age++;}
라는 식의 매서드를 사용하며, 이것은 특정 필드에만 영항을 미친다. 그러나 merge는 모든 필드에 영향을 미치기 때문에, 사실상 사용하는 것이 위험하다. - 그래서 아래와 같은 더치체킹을 주로 사용한다.
더티체킹
Member member2 = new Member();
member2.setName("lee");
member2.setAge(22);
em.persist(member2);
em.flush();
em.clear();
Member updateMember2 = em.find(Member.class, member2.getId());
updateMember2.setAge(23);
em.flush();
em.clear();
Member resultMember2 = em.find(Member.class, member2.getId());
System.out.printf("원래 값 : %s, %d\n", member2.getName(), member2.getAge());
System.out.printf("터티체킹으로 업데이트 한 값 : %s, %d\n", resultMember2.getName(), resultMember2.getAge());
Hibernate:
/* update
jpa9_merge.Member */ update
Member
set
age=?,
name=?
where
id=?
Hibernate:
select
member0_.id as id1_1_0_,
member0_.age as age2_1_0_,
member0_.name as name3_1_0_
from
Member member0_
where
member0_.id=?
원래 값 : lee, 22
터티체킹으로 업데이트 한 값 : lee, 23
- 더티체킹은 영속 객체의 변경점을 엔티티 매니저가 flush 할 때 감지하는 방식이다.
- setAge()를 할 경우, age 필드만 변경된다. 이를 감지하여 age만 변경한다.