JPA(Java Persistence API)

[JPA] 변경감지와 Merge

cwchoiit 2023. 11. 12. 14:25
728x90
반응형
SMALL

데이터베이스에서 관리하고 있는 어떠한 데이터를 변경(수정)할 때 JPA는 어떻게 처리할까?

 

JPA공부를 좀 해보니 영속성 컨텍스트에 관리되고 있는 엔티티는 트랜잭션이 커밋을 하는 시점 (다른 말로 영속성 컨텍스트가 닫히는 시점)에 flush()를 호출하고 그 때 변경된 내용이 있으면 변경 감지(dirty checking)를 통해 update 쿼리가 나간다. 

 

그럼 영속성 컨텍스트가 더는 관리하지 않는 준영속 엔티티는 어떻게 변경할까?

방법은 두가지가 있다. 

- 영속성 컨텍스트가 다시 관리하게 한 후 변경 감지를 사용

- 병합(merge)를 사용

 

 

변경 감지 🟢

@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
    Item findItem = em.find(Item.class,itemParam.getId()); //같은 엔티티를 조회한다.
    findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다. 
}

 

위 코드를 보면 파라미터로 넘어온 아이템은 현재 update() 메소드의 트랜잭션에서 관리되고 있는 엔티티가 아니다. 하나의 트랜잭션은 하나의 영속성 컨텍스트와 매핑된다고 생각하면 가장 이해가 쉽다. 이 메소드가 시작하는 시점에 트랜잭션이 시작되니까 당연히 넘어온 파라미터는 관리되는 엔티티가 아닌 준영속 엔티티라고 볼 수 있다.

 

그래서 이 준영속 엔티티를 다시 영속성 컨텍스트에 넣어버리는 것이다. 어떻게? 조회를 해서.

그리고 조회한 엔티티를 가지고 원하는 변경 내용을 적용한다. 이러고 persist(), merge()가 필요없이 그저 메소드가 끝나는 시점에 트랜잭션이 종료되고 트랜잭션이 종료되는 시점에 커밋이 일어나기 때문에 변경감지가 적용된다.

 

병합 🔴

@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티 
	Item mergeItem = em.merge(itemParam);
}

 

위처럼 merge()를 호출하면 된다. 코드는 이게 더 효율적이고 간단해 보인다. 

그러나, 병합은 가급적 사용하면 안된다. 왜냐하면 병합은 예상하지 못한 문제를 일으킬 수 있다. 어떤 문제냐면 병합은 모든 필드를 교체한다. 병합 시 값이 없으면 null로 업데이트 할 위험이 있다. 

 

728x90
반응형
SMALL

결론

엔티티를 변경할 때는 항상 변경 감지를 사용하자.

 

- 컨트롤러에서 엔티티를 생성하지 말자.

- 트랜잭션이 있는 서비스 계층에 식별자(id)와 변경할 데이터를 명확하게 전달하자(DTO객체 또는 파라미터)

- 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회한 후 엔티티의 데이터를 직접 변경하자.

- 트랜잭션 커밋 시점에 변경 감지가 실행

 

예시)

 

Controller

 @Controller
@RequiredArgsConstructor
public class ItemController {
    private final ItemService itemService;

    @PostMapping(value = "/items/{itemId}/edit")
    public String updateItem(@PathVariable Long itemId,
                             @ModelAttribute("form") BookForm form) {
        itemService.updateItem(
        		itemId,
                form.getName(),
                form.getPrice(),
                form.getStockQuantity());
        return "redirect:/items";
    }
}

 

Service

@Service
@RequiredArgsConstructor
public class ItemService {
  private final ItemRepository itemRepository;

  @Transactional
  public void updateItem(Long id, String name, int price, int stockQuantity) {
          Item item = itemRepository.findOne(id);
          item.setName(name);
          item.setPrice(price);
          item.setStockQuantity(stockQuantity);
  } 
}

 

728x90
반응형
LIST