본문 바로가기
Spring/JPA

[JPA] 준영속 엔티티를 변경하는 방법 - 변경 감지 / 병합(Merge)

by 코딩균 2022. 2. 15.

* 인프런 김영한님 강의와 <자바 ORM 표준 JPA 프로그래밍> 책을 보고 정리한 내용입니다

 

준영속 엔티티

  • Persistence context가 관리하지 않는 엔티티
  • 영속성 컨텍스트가 제공하는 기능(1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩 등)을 사용할 수 없는 엔티티
  • 영속성 컨텍스트의 관리를 한번 받았으므로 identifier는 가지고 있음

 

영속 엔티티를 준영속 엔티티로 만드는 방법

  • em.detach() : 영속성 컨텍스트 안의 1차 cache, 쓰기 지연 SQL 저장소에서의 해당 엔티티 정보 제거
  • em.clear() : 영속성 컨텍스트 안에 있는 모든 정보를 초기화
  • em.close() : 영속성 컨텍스트 종료

 

 

즉, 준영속 엔티티 member가 존재한다고 가정했을 때,

member.setName("stody");

영속 엔티티라면 DB에 반영(변경 감지를 통해)이 되겠지만

준영속 엔티티이기 때문에 DB에 반영되지 않음. 그냥 메모리상의 객체로만 남는 것

 

 

영속성 컨텍스트의 변경 감지 사용

준영속 엔티티가 파라미터로 넘어온 경우, 

해당 identifiet (id)를 기반으로 다시 db에서 entity를 조회해와서 영속성 컨텍스트에서 관리하도록 만듬

 

* 준영속 엔티티는 한번 db를 거쳐온 것이기 때문에 id를 가지고 있음

 

  1. transaction 안에서 엔티티를 재조회
  2. commit 시점에서 1차 캐시에서의 Dirty bit checking을 통해 변경 감지
  3. UPDATE SQL문을 생성하여 실행 
@Transactional
public void updateItemPrice(Item item, int price){
	Item findItem = itemRepository.findOne(item.getId());
    // 영속성 컨텍스트에 들어간 findItem
    
    findItem.setPrice(price);
    // transaction이 끝나며 commit -> flush(영속성 엔티티중에서 변경된것 다 찾는다) -> update query 날림
}

 

 

병합(merge) 사용

준영속 상태의 엔티티를 영속 상태로 변경시 사용

준영속 상태의 엔티티에서 값이 없는 필드가 있다면 null로 업데이트 시켜버림

@Transactional
void updateItem(Item item){
	Item mergeItem = em.merge(item);
}
  1. item의 id를 기반으로 1차 캐시에서 엔티티 조회
  2. 없는 경우 db에서 조회하여 1차 캐시에 저장
  3. 파라미터로 넘어온 준영속 상태의 모든 필드 값을 새로운 영속성 엔티티에 넣는다
  4. 준영속 상태인 item과는 다른 새로운 엔티티를 반환

merge를 풀어써보면 결국에

@Transactional
public Item merge(Long item){

	Item findItem = itemRepository.findOne(item.getId());
    // 영속성 컨텍스트에 들어간 findItem
    
    findItem.setPrice(item.getPrice()); // price만 update하는 로직이었다고 가정
    findItem.setName(item.getName());
    findItem.setStockQuantity(item.getStockQuantity());
    // transaction이 끝나며 commit -> flush(영속성 엔티티중에서 변경된것 다 찾는다) -> update query 날림
    return findItem;
}

원래 의도는 price만 변경하려고 하는데 

merge를 사용하게 되면 price 뿐만 아니라 다른 필드값들도 복사되어서 새로운 영속 컨텍스트 객체로 옮겨진다

 

 

결론

변경 감지는 특정 필드만 변경 가능

merge는 특정 필드만 변경하려해도 모든 필드가 건드려짐

-> merge를 사용 직전에 준영속 컨텍스트가 잘못 만져져서 변경할 필드가 아닌 다른 필드가 비었을 경우 null로 update되는 위험이 있음

 

db의 row의 특정 정보를 update할 때는 변경감지를 쓰는 것이 적절하다

service 단에 DTO 혹은 준영속 엔티티의 id값을 파라미터로 넘겨서 조회후 영속성 컨텍스트 상태에서 변경감지 방식을 사용하여 update하는 것이 안전

@Transactional
public void updateItemPrice(Item item, int price){
	Item findItem = itemRepository.findOne(item.getId());
    // 영속성 컨텍스트에 들어간 findItem
    
    findItem.setPrice(price);
    // transaction이 끝나며 commit -> flush(영속성 엔티티중에서 변경된것 다 찾는다) -> update query 날림
}
@Transactional
public void updateItemPrice(Long itemId, int price){
	Item findItem = itemRepository.findOne(itemId);
    // 영속성 컨텍스트에 들어간 findItem
    
    findItem.setPrice(price);
    // transaction이 끝나며 commit -> flush(영속성 엔티티중에서 변경된것 다 찾는다) -> update query 날림
}