변경 감지(Dirty Checking) & 병합(Merge)
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 에서 더티 체킹을 시도하게 된다.
개념을 이해해보고, 실제로 적용해보았다!.. 보람차다!