참고 : 인프런 [ 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 김영한 ]
전에 실습한 주문 서비스에서
~domain/Order.java
//==비즈니스 로직==//
/**
* 주문취소
* */
public void cancle(){
if(delivery.getStaus()==DeliveryStatus.COMP){ //배송 완료 상태
throw new IllegalStateException("이미 배송완료된 상품은 취소가 불가능 합니다.");
}
this.setStatus(OrderStatus.CANCLE);
for(OrderItem orderItem : orderItems){ //한번 주문할때 고객이 상품 2개를 주문한다면 각각의 상품도 cancle상태로 변경
orderItem.cancle();
}
부분을 확인해보자.
this.setStatus(OrderStatus.CANCLE); 을 통해 엔티티의 값을 변경하기만하면, 따로 DB와 연결된 update나 머지와 같은 메서드를 사용하지 않아도
JPA가 트랜잭션 커밋 시점에서 변경 부분을 찾아서 DB 업데이트를 날리고 트랜잭션을 커밋하게 된다.
이 과정에서 플러시할때 Dirty Checking이 일어난다.
Dirty Checking
- JPA에서 엔티티의 상태 변화를 감지하여 자동으로 데이터베이스에 업데이트 쿼리를 실행하는 기능
- 엔티티 객체의 상태를 지속적으로 추적하여 변경된 부분만을 데이터베이스에 반영
- 개발자는 데이터베이스 업데이트 작업을 명시적으로 처리할 필요 없이, 객체의 상태를 변경하면 JPA가 이를 자동으로 반영
+
플러시(Flush): 트랜잭션이 커밋되기 전에, 엔티티 매니저는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영.
이 과정에서 더티 체킹이 일어나고, 변경된 엔티티에 대한 SQL 쿼리가 생성
@PostMapping("/items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form){
Book book=new Book();
book.setId(form.getId());
book.setName(form.getName());
book.setPrice(form.getPrice());
book.setStockQuantity(form.getStockQuantity());
book.setAuthor(form.getAuthor());
book.setIsbn(form.getIsbn());
itemService.saveItem(book);
return "redirect:/items";
}
(여기서는 itemService.saveItem(book) 에서 수정을 시도하는 Book 객체다. Book 객체는 이미 DB에 한번 저장되어서 식별자가 존재한다. 이렇게 임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속 엔티티로 볼 수 있다.)
merge ) 사용ItemService
//==변경 감지 기능//
@Transactional
public void update(Item itemParam){ //itemParam: 파라미터로 넘어온 준영속 상태의 엔티티
Item findItem= itemRepository.findOne(itemParam.getId()); //같은 엔티티 조회
findItem.setPrice(itemParam.getPrice()); //데이터 수정
}
트랜잭션 안에서 엔티티를 다시 조회, 변경할 값 선택 -> 트랜잭션 커밋 시점에 변경 감지(Dirty Checking)이 동작해서 데이터베이스에 UPDATE SQL 실행
이 방법을 사용하면 itemRepository의 save(Item item)을 호출할 필요가 X
ItemRepository
public void save(Item item){
if(item.getId()==null){
em.persist(item); //신규등록
}else{
em.merge(item); //업데이트
}
}
ItemService의 save 를 살펴보면 id값이 존재하면, merge(item)를 호출하게 되어있다.
merge()
- 준영속 상태의 엔티티를 영속 상태로 전환하고, 데이터베이스와 동기화하는 데 사용
- 영속성 컨텍스트에 존재하지 않는 엔티티의 변경 내용을 데이터베이스에 반영
merge()의 동작 원리
- 준영속 상태의 엔티티 전달: merge() 메서드에 준영속 상태(또는 비영속 상태)의 엔티티를 전달.
- 새로운 영속 상태의 엔티티 반환: merge()는 해당 엔티티를 복사한 새 영속 상태의 엔티티를 반환. 기존의 준영속 상태 엔티티는 여전히 준영속 상태로 남아 있음.
- 변경 사항 반영: 반환된 영속 상태의 엔티티는 영속성 컨텍스트에서 관리되므로, 트랜잭션이 커밋될 때 변경 사항이 데이터베이스에 반영.
즉, 트랜잭션이 커밋될때, merge를 통해 반환된 영속 상태의 엔티티와 id 값이 같은 엔티티를 데이터베이스에서 찾아 모든 데이터를 바꿔치기 한다.
주의: 변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다. 병합시 값이 없으면 null 로 업데이트 할 위험도 있다. (병합은 모든 필드를 교체한다.)
가장 좋은 해결 방법
merge보다는 변경감지를 사용하는 것이 더 좋다.
id )와 변경할 데이터를 명확하게 전달한다.