본문 바로가기
Back-end/java

[SpringBoot-JPA활용] 변경 감지와 병합(merge)

by somlang_bba 2023. 3. 3.
728x90

JPA에서 영속성 컨텍스트에서 관리하는 엔티티의 경우 EntityManger의 flush 메소드가 발생하는 시점에 변경감지(dirty checking)를 통해 엔티티의 값에 변화가 있는 경우 update 쿼리를 날려 DB에 적용해준다. 하지만 준영속 엔티티의 경우 영속성 컨텍스트 안에 위치한 것이 아니기 때문에 변경감지 대상에서 빠진다.

 

준영속 엔티티

- EntityManager의 persist를 통해 한번 JPA를 탔던 엔티티로 식별값을 가진 엔티티이지만 실제 영속성 컨텍스트에는 들어가 있는 않은 엔티티

 

ex ) 상품 수정을 위해 프론트 단에서 넘어온 데이터로 만들어진 객체(상품 수정을 위해 식별 값을 가지고 있음)

 

 그렇다면 준영속 엔티티를 수정하는 방법에는 뭐가 있을까?

 

첫번째로 병합(merge) 기능을 사용한다.(추천하지 않는 방법: 이유는 마지막에 설명)

  - 명시적으로 EntityManager의 merge 메서드를 호출해서 준영속 상태를 영속성 상태로 바꾸어준다. 아래의 코드에서 보면 ItemRepository의 save 메서드에서 item의 식별자인 itemId의 존재 여부에 따라 persist 혹은 merge를 수행하도록 하고 있다.

class ItemService {

    private final ItemRepository itemRepository; 
	...
    /**
    	여기에서 사용하는 item은 다음과 같다. (수정 폼에서 받아온 이미 등록한 데이터)
        { "itemId" : 1, "name" : "any" }
    */
    @Transactional
    public void update(Item item) {
    	item.setName("name1");
        itemRepository.save(item); // merge를 호출하기 위한 코드임
    }
	...
}

class ItemRepository {
	private final EntityManager em;
    ...
    public void save(Item item) {
    	if (item.getItemId() == null) {
        	em.merge(item);	
        } else {
        	em.persist(item);
        }
    }
    ...
}

 

728x90

 

두번째로 변경감지(dirty checking)를 사용한다.

 - 위에서는 사용 불가하다면서 뭔소리야? 싶겠지만 이제부터 설명하겠다. 그리고 이 방법을 더욱 좋은 방법으로 추천한다. 위와 같은 코드에서 바뀌는 부분을 보자

 

class ItemService {

    private final ItemRepository itemRepository; 
	...
    /**
    	여기에서 사용하는 item은 다음과 같다. (수정 폼에서 받아온 이미 등록한 데이터)
        { "itemId" : 1, "name" : "any" }
    */
    @Transactional
    public void update(Item item) {
    	Item changeItem = itemRepository.findOne(item.getItemId());
    	changeItem.setName(changeItem.getName());
        // itemRepository.save(item); // 없어도 됨
    }
	...
}

class ItemRepository {
	private final EntityManager em;
    ...
    /* 필요없음 => update 동작이 Service에서 처리 됨.
    public void save(Item item) {
    	if (item.getItemId() == null) {
        	em.merge(item);	
        } else {
        	em.persist(item);
        }
    }
    */
    ...
}

 

ItemRepository의 save 메서드가 사라졌다. 이는 JPA의 특성상 서비스에 걸려있는 Transaction이 닫히면서 DB에 변경내용을 Commit 하는 과정에서 EntityManager의 flush안에서 Dirty Checking이 일어나고 변경된 부분에 있어서 update query를 보내기 때문이다. 

 

그렇다면 변경감지를 병합보다 더 추천하는 이유는 무엇일까? 코드의 양을 줄일 수 있다거나 하는 시시콜콜한 이유가 될 수도 있겠지만 EntityManager의 merge 메서드의 동작방식에 주의해야할 점이 있기 때문이다. merge메서드는 해당 엔티티의 모든 속성(property)들을 모두 업데이트 한다. 즉, 해당 속성 값이 들어오지 않는 상황이라면 해당 값을 null로 DB에 update 쿼리를 날려버리는 것이다.

 

실무를 진행하다보면 처음 등록 상황에서 값을 정해줄 수 있는 것들이 한번 등록하고 나면 변경할 수 없도록 하는 (회원가입 시 이름, 주민등록번호 등을 직접 입력해서 넣어줄 수 있지만 수정에서는 불가한 것 처럼) 요구사항들이 있다. 이런 경우 merge만으로 비즈니스 로직을 수행한다면 수정하는 과정에서 해당 값들에 null이 들어가는 불상사가 생길 수 있다.

 

변경감지를 이용한다면 변경된 속성에 대해서만 update query를 날리기 때문에 위와같은 오류를 방지할 수 있게 되는 것이다.

728x90