Spring/JPA

[JPA] 변경 감지와 병합(merge) ,데이터수정

김긍수 2021. 3. 22. 20:05
변경감지와 병합의 차이는 정말 중요한 내용이므로 꼭 이해해야한다.

준영속 엔티티?

영속성 컨텍스트가 더 이상 관리하지 않는 엔티티를 말한다. (Book 객체는 이미 DB에 한번 저장되어서 식별자가 존재한다. 이렇게 임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속엔티티라고 볼 수 있다.)

Book book = new Book();
book.setId(form.getId());
book.setName(form.getName());
book.setPrice(form.getPrice());
book.setStockQuantity(form.getStockQuantity());
book.setAuthor(form.getAuthor());
book.setIsbn(form.getIsbn());

준영속의 문제

- JPA가 관리를 안함. 그러므로 아무리 값을 바꿔도 업데이트 쿼리가 실행되지 않는다.

 

준영속 엔티티를 수정하는 방법

  1. 변경 감지 기능 사용 (Dirty Checking)
  2. 병합(merge) 사용

 

변경감지 (DirtyChecking)를 사용하는 방법

영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법

트랜잭션 안에서 엔티티를 다시 조회, 변경할 값 선택 -> 트랜잭션 커밋 시점에서 변경 감지

이 동작을 해서 DB에 업데이트 SQL을 실행한다.

@Transactional
    public void updateItem(Long itemId, Book param) { //param : 파리미터로 넘어온 준영속 상태의 엔티티
        Item findItem = itemRepository.findOne(itemId); // itemId를 기반으로 DB에 있는 실제 영속상태의 엔티티를 가져옴.
        findItem.setPrice(param.getPrice());
        findItem.setName(param.getName());
        findItem.setStockQuantity(param.getStockQuantity());
        findItem.setPrice(param.getPrice());

        /*
            -변경감지-
            itemRepository.save(findItem); <- 이런 걸 호출할 필요가 없다.
            왜냐하면 findItem은 영속상태이다.
            값 셋팅이 완료된 후 트랜잭션에 의해 커밋이 된다.
            커밋이 되면서 JPA는 flush를 날린다. (영속성 컨텍스트에 있는 엔티티 중 변경된 것을 찾는다.)
         */
    }
Book book =  em.find(Book.class, 1L);

book.setName("변경이름");

//변경감지
//commit

트랜잭션이 커밋하면 변경한 것을 JPA가 찾아서 업데이트 쿼리를 자동으로 생성하여 DB에 반영하는 것이다.

 

병합 (Merge)을 사용하는 방법(최대한 사용하지 않기)

준영속 상태의 엔티티를 영속 상태로 변경할 때 사용한다.

@Transactional
	void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
	Item mergeItem = em.merge(item);
}

 

병합 동작 방식

  1. merge()를 실행한다.
  2. 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.
    1. 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고, 1차 캐시에 저장한다.
  3. 조회한 영속 엔티티(mergerMember)에 member엔티티의 값을 채워 넣는다. (member 엔티티의 모든 값을 mergeMember에 밀어넣는다. 이때 mergeMember의 "회원1"이라는 이름이 "회원명변경"으로 바뀐다.)
  4. 영속 상태인 mergeMember를 반환한다.

병합시 동작 방식을 간단히 정리

  1. 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다.
  2. 영속 엔티티의 값을 준 영속 엔티티의 값으로 모두 교체한다.(병합)
  3. 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 DB에 업데이트 SQL이 실행된다.
@Transactional
    public Item updateItem(Long itemId, Book param) {
        Item findItem = itemRepository.findOne(itemId); // itemId를 기반으로 DB에 있는 실제 영속상태의 엔티티를 가져옴.
        findItem.setPrice(param.getPrice());
        findItem.setName(param.getName());
        findItem.setStockQuantity(param.getStockQuantity());
        return findItem; // merge
        // 이렇게 세터로 하지말고, 메소드를 만드는 것이 좋다.
        /*
            -변경감지- 반환값 void
            itemRepository.save(findItem); <- 이런 걸 호출할 필요가 없다.
            왜냐하면 findItem은 영속상태이다.
            값 셋팅이 완료된 후 트랜잭션에 의해 커밋이 된다.
            커밋이 되면서 JPA는 flush를 날린다. (영속성 컨텍스트에 있는 엔티티 중 변경된 것을 찾아서 있다면 업데이트쿼리를 날린다.)
         */
    }

 

주의 : 변경 감지기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다.
병합 시 값이 없으면 null로 업데이트될 수 있다.

따라서 병합보다는 변경감지기능을 사용하는 것이 좋다. 최대한 Merge는 쓰지 않는게 좋다.

 

*** 엔티티를 변경할 경우 항상 변경 감지를 사용해야한다.