본문 바로가기
JPA Basic

변경 감지(Dirty Checking) & 병합(Merge)

by 완두완두콩 2022. 3. 21.

JPA 에는 따로 Update 에 관한 쿼리문이 존재하지 않고 ( @Query로 만드려면 만들겠지만 ) Update 를

변경 감지와 병합

두 방법을 통해서 실행하게 된다.

 

변경 감지(Dirty Checking) 과 병합(merge) 에 대해서 알기 전에

영속 컨텍스트와 준영속 엔티티 두 개념에 대해서 알아야 한다.


영속성 컨텍스트란 ? 

JPA 에 존재하는 엔티티 매니저를 통해 쿼리문을 날리면 자동으로 해당 엔티티는

영속성 컨텍스트에 들어가서 트랜잭션이 끝나는 시점까지 따로 관리하게 된다.

 

앞선 JPA 포스팅에서 다룬적이 있는데, 

Member member = new Member(); 로 객체만 생성한 경우

비영속 상태

 

memberService.save(member) 처럼 DB에 쿼리를 날린 경우

영속 상태

 

쿼리가 끝나고 해당 트랜잭션이 모두 끝나면

준영속 상태

 

로 존재하게 된다.

 

우리가 관심있게 봐야 할 부분은 영속 상태와 준영속 상태이다.


변경 감지(더티 체킹)는 바로 영속 컨텍스트에 들어가 있는 영속 엔티티에 한해서

Update 쿼리를 날려준다.

 

예를 들어, 다음과 같은 코드가 있다고 가정해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    /**
     * 변경감지
     */
    @Transactional
    public void updateItemV1(Long itemId, Book param){
        Item findItem = itemRepository.findOne(itemId);
        findItem.setName(param.getName());
        findItem.setPrice(param.getPrice());
        // save 호출할 필요가 있을까? 없음.
        // 이미 findItem 은 영속상태이기 때문에  @Transactional 에 의해서 자동 커밋이 됨.
        // flush (영속성엔티티중에 변경된 엔티티가 무엇인지 자동으로 찾는 과정)
        // 변경된 엔티티 확인하면 자동으로 update 쿼리를 날려준다.
    }
    
cs

updateItemV1 이라는 트랜잭션 안에서

itemRepository.findOne() 을 통해 찾아진 findItem 은 현재 영속성 컨텍스트 내부에

영속 엔티티로 존재하게 된다.

 

해당 영속 엔티티를 .setName, .setPrice 등으로 값을 save 하면 새로 commit 쿼리가

호출되는 것이 아니라 현재 찾아진 값을 영속 컨텍스트에서 쭉 확인하고

달라진 값이 있으면 flush 를 통해 변경된 엔티티에 자동으로 update 쿼리를 날려준다.

 

이를 변경감지(더티체킹)라고 하며, 이는 반드시 영속성 엔티티로 존재할 때만 가능하다.

 


반면에, 병합은 영속성 엔티티로 존재하지 않아도 Update 문을 날려준다.

   
1
2
3
4
5
6
7
8
9
    private final EntityManager em;
 
    public void save(Item item) {
        if (item.getId() == null) {
            em.persist(item);
        } else {
            em.merge(item);
        }
    }
cs
   

위의 코드에서 보면, save 를 호출시에 getId 를 통해 해당 id 값이 존재하면

persist 를 호출하고, 이미 존재하는 경우에는 em.merge 를 통해 병합을 시도한다.

 

변경 감지처럼 굳이 영속성 컨텍스트에 영속 엔티티가 존재할 필요 없이, 

.save 를 호출하면 자동으로 Update 가 되는 것이다.

 

병합이 언뜻 보기에는 더 편해보이지만 병합의 가장 큰 문제는 바로

내가 설정하지 않은 값의 경우에는 null 로 들어간다는 것이다.

 

예를 들어, 상품ID, 상품명, 가격, 생성 날짜 총 4개의 파라미터가 있다고 가정할 때

상품명, 가격에만 값을 넣고 .save 를 호출하면

그 외의 상품ID , 생성 날짜는 값이 null 로 Update 되게 된다.

 

그렇기 때문에 병합은 사용하지 않고 변경 감지만 사용한다고 앞으로

알아두면 될 것 같다.

정리 내용

 

 

이를 실제로 내가 현재 작업중인 프로젝트에 적용해 보았다.(FullCalendar)

DTO 단에 Update 메소드를 작성해준다. (내가 Update 하고자하는 파라미터만 적용)

Dto 에 따로 뺀 이유는 현재는 파라미터 값이 적지만, 나중에 많아지는 경우

.set 을 통해 하나하나 값을 설정하면 추적이 힘들어진다.

그래서 dto 에 값이 어디서 변경되는지를 알 수 있도록 만들어주었다.

Service 단에 update 메소드를 작성해준다.

이 때 , Repository 에서 findById 를 통해 managerAssignSchedule 은 현재 영속성 엔티티로 관리가 되고 있다는

점이 가장 중요한 부분이다.

 

그리고 managerAssignSchedule.update(dto, dto)를 날려주면

변경 감지가 일어나게 되며 트랜잭션이 끝나는 시점에서 flush 하고, 변경된 값을 update 시켜준다.

(dto 로 보다 깔끔하게 어디서 변경되는지 추적 가능)

 

그리고 마지막으로 Controller 단에서 변경하고자 하는 값을 dto에 넣고,

해당 컬럼의 ID를 반환, Dto 로 반환 하게 되면

Service 에서 더티 체킹을 시도하게 된다.

 


 개념을 이해해보고, 실제로 적용해보았다!.. 보람차다!

'JPA Basic' 카테고리의 다른 글

지연 로딩 & 조회  (0) 2022.03.23
JPA Basic - 6  (0) 2021.12.30
JPA Basic - 5  (0) 2021.12.29
JPA Basic - 4  (0) 2021.12.28
JPA Basic - 3  (0) 2021.11.18

댓글