Dev/Spring

준영속과 변경감지

oxdjww 2023. 10. 25. 15:06
728x90
반응형

Intro

본 포스팅에서는 준영속 엔티티를 수정하는 두 가지 방법에 대해 다룬다.

준영속 엔티티란?

엔티티의 생명주기에 따른 속성 분류는 다음과 같다.

  • 비영속
    • 영속성 컨텍스트와 연관이 없는 상태. 그저 생성만 된 객체를 의미한다.
  • 영속
    • 영속성 컨텍스트가 관리하는 객체
  • 준영속
    • 영속성 컨텍스트가 관리했던 객체. 즉, Database에 한번 들어갔다 나온 객체 (식별자 존재)
  • 삭제
    • Database에서 삭제된 객체

위의 설명과 같이 준영속 엔티티란 영속성 컨텍스트가 더이상 관리하지 않는 객체를 의미한다.
하지만 데이터베이스에 한번 저장되었기에 식별자가 존재한다.
식별자를 이용해 임의의 새로운 객체를 생성한 경우에도 준영속 엔티티라 칭한다.

이런 준영속 엔티티의 값을 변경하는 방법에는 변경감지/병합 두 가지 방법이 있다.

변경감지 (Dirty Checking)

영속성 컨텍스트가 관리할 때, 즉 Transaction 내에서는 값을 변경하면, database에 직접 접근하지 않아도 Transaction commit 시점에 JPA가 변경점을 체크하여 database에 반영한다.

@PostMapping("items/{itemId}/edit"){
    public String updateItem(@PathVariable String itemId, @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); 코드가 없다면 값의 변경이 database에 전달되지 않는다.

그렇기에 Controller가 아닌, 영속성 컨텍스트에 의해 관리되는 Service딴에서 코드를 구현한다.

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

Transactional에 의해 commit이 되고, commit된 내용을 JPA가 flush(UPDATE SQL)하여 database에 반영된다.
즉, 영속성 컨텍스트 안에서 엔티티를 다시 조회하고 데이터를 수정하는 과정을 통해 엔티티의 값을 변경한다.

병합

병합 기능(merge())을 통해 준영속 엔티티를 영속 엔티티로 변경할 수 있다.

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

병합 동작 방식

  1. merge()를 실행한다.
  2. 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.
    • 만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고, 1차 캐시에 저장한다.
  3. 조회한 영속 엔티티 mergeMembermember엔티티의 값을 채워 넣는다.
    • 이 때, member 엔티티의 모든 값을 덮어씌운다.
  4. 영속 상태인 mergeMember를 반환한다.

| 주의점

  • 변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다. 병합 시에 값이 없는 속성은 null로 데이터베이스에 업데이트 될 위험이 있다.

실무에서는..

병합을 사용하며 null로 업데이트 되는 위험을 감수하려면, 변경 폼 화면에서 모든 데이터를 항상 유지해야 한다.
하지만 이는 변경 가능한 데이터만 노출하고 다루기 때문에 번거롭다.

그러므로 엔티티의 속성을 변경할 때는 항상 변경 감지를 사용하는 것이 좋다.

  • 컨트롤러에서 어설프게 엔티티를 생성하지 않는다. (준영속 엔티티가 되어 처리가 어려움)
  • 트랜젝션이 있는 서비스 계층에 식별자(id)와 변경할 데이터를 명확하게 전달한다. (파라미터 or DTO)
  • 트랜젝션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경한다.
  • 트랜젝션 커밋 시점에 변경 감지가 실행된다.


감사합니다.

Ref

실전! 스프링 부트와 JPA 활용 1, 김영한 강사님

728x90
반응형