본문 바로가기

Spring boot

[spring boot] 변경감지(dirty checking)와 병합(merge)

변경 감지 동작 원리

변경 내용을 JPA가 자동으로 인식해 commit할 때 반영해줌.

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작

// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);

//!!!!! em.update(member) 이런 코드가 있어야 하지 않을까?

transaction.commit(); // [트랜잭션] 커밋

!! em.update는 필요하지 않다. 


commit시 로직

출처 : 김영한.(2019).자바 ORM 표준 JPA 프로그래밍 - 기본편(강의자료)

1. 영속 컨텍스트에 객체가 들어오면 바로 스냅샷 찍어두는데 commit할 때 내부적으로 flush호출하고 스냅샷과 엔티티 일일히 다 비교한다.

2. 변경사항 생기면 쓰기 지연 SQL저장소에 update 쿼리를 만든다.

3. update 쿼리를 DB에 반영하고 commit 한다. 

 

*remove도 동일 (update 쿼리가 아닌 delete 쿼리 만든다.)

 

 

 

 

영속상태 DB에 반영하기 (변경감지 이용)

엔티티가 영속상태면 데이터가 변경될때 jpa가 커밋(정확히는 flush)시점에 알아서 변경감지를 해 변경된 데이터를 DB에 반영해줌.


Order의 cancel 메서드보면 알아서 DB 반영을 해주기 때문에 this.setState(OrderStatus.CANCEL); 만 해두고 em.persist나 em.merge안 함.

--------------

새로운 빈 껍데기 Book book = new Book()으로 만들어진 객체지만 book.setId(form.getId())로 id가 이미 채워진것 (@GeneratedValue로 인해서 id가 만들어지지 않은 인스턴스)은 준영속상태임 = merge(원래 스냅샷과 달라진 것을 찾아서 update. 아래 1번과 같은 코드를 jpa가 실행해줌.)해줘야함. 

 

 

 

준영속상태 DB에 반영하기

1. 변경 감지

@Transactional 안에서 findOne(id)(이건 id를 이용해 영속상태인걸 찾아온것)해서 findItem을 만들고 파라매터로 데이터를 받아 findItem.set~으로 이미 영속상태의 findItem의 값들을 바꿔치기 해줌. Transactional 끝나면 commit되고 그럼 jpa가 알아서 flush(변경된 게 있나찾기)해 DB에 반영시켜줌. 

 

2. merge 원리 

  1. 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다.
  2. 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체한다.(병합한다.)
  3. 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행

merge(member)하면 member id로 1차캐시에서 찾고 없으면 DB에서 가져옴. 가져온게 memberResult면 member값들을 memberResult.set~(member.get~())으로 memberResult에 채워넣어줌. 그리고 memberResult반환

- member는 영속성 컨텍스트에 들어가지 않고 반환되는 memberResult만 영속성 컨텍스트에 의해 계속 관리됨. 

 

 

 

 

주의 merge는 모든 값을 변경시킨다. 병합할 값이 null이면 null로 업데이트 되기 때문에 merge보다 원하는 속성만 선택해 변경시킬 수 있는 1번 방법(변경 감지 기능)이 안전

 

=> 실무에서는 보통 변경가능한 데이터만 노출하기 때문에, 병합을 사용하는 것이 오히려 번거롭다.

 

 

핵심! 가장 좋은 방법

엔티티를 변경할 때는 항상 변경 감지를 사용하세요.

 

※ 아래는 다 같은말이며 사진을 참고하세요.

  • 컨트롤러에서 어설프게 엔티티를 생성하지 마세요.
  • 트랜잭션이 있는 서비스 계층에 식별자( id )와 변경할 데이터를 명확하게 전달하세요.(파라미터 or dto)
  • 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경하세요.
  • 트랜잭션 커밋 시점에 변경 감지가 실행됩니다.

      - Controller는 이정도까지만

      - Service에서 진짜로 생성 및 변경하기

노랑색 : 변경은 set보단 change같이 의미있는 메서드로 만들어주기