JAVA

[JAVA] 변경감지와 병합(merge)

응디 2022. 2. 10. 17:29

준영속 엔티티

  • 영속성 컨텍스트가 더이상 관리하지 않는 엔티티

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

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

변경감지기능 사용(itemService에서 작성)

/**
* 변경감지
*/
@Transactional
public void updateItem(Long itemId, Book param){
     Item findItem = itemRepository.findOne(itemId);
     findItem.setName(param.getName());
     findItem.setPrice(param.getPrice());
     findItem.setStockQuantity(param.getStockQuantity());
   
}

여기서 itemRepository.save 를 호출할 필요가 없다. ( findItem은 DB에서 가져온 영속 상태 값 )

왜냐하면 @Transactional이 실행됨과 동시에 commit이 되면서 flush가 날라간다.

flush 되면서 수정된 부분을 jpa가 확인하고 그 부분을 알아서 DB에 반영함(update 쿼리 날림)

 

 

병합 사용

  • 준영속 상태를 영속 상태로 변경할 때 사용
public void save(Item item){
        if(item.getId() == null){
            em.persist(item);
        }else{
            Item merge = em.merge(item); // update 비슷한 것?
            // merge는 받아온 파라미터로 모두 교체해버린다. 병합이 되면 merge 값은 영속성 컨텐스트에서 관리가 되며 이 값을 사용해야한다.
            // but item에 있는 값중에 null이 있다면 null도 바꿔치기 해버린다 그래서 사용하면 X -> 위험
        }
    }

 

병합 동작 방식

  1. merge() 실행
  2. 파라미터로 넘어온 준영속 엔티티 식별자 값으로 1차 캐시에서 엔티티 조회 (이때 1차 캐시에 없으면 DB 조회 후 1차 캐시에 저장)
  3. 조회한 영속 엔티티 merge에 item 엔티티 값을 채워넣음( item의 모든 값을 merge에 채워넣음 )
  4. 영속 상태인 merge를 반환함

 

이걸 변경감지 코드로 풀어내면 아래와 같다.

/**
* merge의 동작 방식
*/
@Transactional
public Item mergeItem(Long itemId, Book param){
     Item findItem = itemRepository.findOne(itemId);
     findItem.setName(param.getName());
     findItem.setPrice(param.getPrice());
     findItem.setStockQuantity(param.getStockQuantity());
     return findItem;
}

 

병합 사용 시 주의 할 점

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

→ 병합은 웬만하면 그냥 사용하지 말 것!

 

 

가장 좋은 해결 방법

엔티티를 변경할 때는 항상 변경 감지 사용

 

 

참고사항

  • 컨트롤러에서 어설프게 엔티티 생성하지 말라.
  • 트랜잭션이 있는 서비스 계층에 식별자와 변경할 데이터 명확하게 전달
  • 서비스 계층에서 영속상태 엔티티를 조회하고, 엔티티의 데이터 직접 변경
  • 커밋시점에 변경 감지 실행됨

 

어설프게 엔티티를 생성 한 예

 

아래 처럼 수정하는게 훨씬 깔끔하다.

 

< controller >

@PostMapping("/items/{itemId}/edit")
public String updateItem(@PathVariable("itemId") Long itemId, @ModelAttribute("form") BookForm form){

     itemService.updateItem(form.getId(), form.getName(), form.getPrice());

     return "redirect:/items";
}

 

<service>

/**
* 변경감지
*/
@Transactional
public void updateItem(Long itemId, String name, int price){
     Item findItem = itemRepository.findOne(itemId);
     findItem.setName(name);
     findItem.setPrice(price);
}

'JAVA' 카테고리의 다른 글

[JAVA] @Query에서 DTO로 조회하기  (0) 2022.02.15
[JAVA] @NamedQuery 와 @Query  (0) 2022.02.15
[JAVA] AllArgsConstructor vs RequiredArgsConstructor  (0) 2022.02.10
[JAVA] 쿼리 파라미터 남기기  (0) 2022.01.20
[JAVA] fetch join  (0) 2022.01.18