Dirty Checking vs. EntityManager.merge()

4936 단어 JPASpringbootJPA

ItemController

@Controller
@RequiredArgsConstructor
@RequestMapping("/items")
public class ItemController {

    private final ItemService itemService;

    @GetMapping("/new")
    public String createItem(Model model) {
        model.addAttribute("form", new BookForm());

        return "items/createItemForm";
    }

    @PostMapping("/new")
    public String createItem(BookForm bookForm) {
        Book book = Book.createBook(bookForm.getName(), bookForm.getPrice(),
                bookForm.getStockQuantity(), bookForm.getAuthor(), bookForm.getIsbn());

        itemService.saveItem(book);

        return "redirect:/items";
    }

    @GetMapping("")
    public String getItems(Model model) {
        List<Item> items = itemService.findItems();
        model.addAttribute("items", items);

        return "items/itemList";
    }

    @GetMapping("/{id}/edit")
    public String updateItemForm(Model model, @PathVariable Long id) {
        Book findBook = (Book) itemService.findOne(id);

        BookForm bookForm = new BookForm();
        bookForm.setId(findBook.getId());
        bookForm.setName(findBook.getName());
        bookForm.setPrice(findBook.getPrice());
        bookForm.setStockQuantity(findBook.getQuantity());
        bookForm.setAuthor(findBook.getAuthor());
        bookForm.setIsbn(findBook.getIsbn());

        model.addAttribute("form", bookForm);

        return "items/updateItemForm";
    }

    @PostMapping("/{id}/edit")
    public String updateItem(@ModelAttribute("form") BookForm form, @PathVariable Long id) {
        Book book = Book.createBook(form.getName(), form.getPrice(),
                form.getStockQuantity(), form.getAuthor(), form.getIsbn());
        book.setId(form.getId());
        itemService.saveItem(book);

        /**
         *  id를 수정할 Book의 id로 set을 하지 않으면, 수정 정보의 값으로 새로운 Book이 생성됨 => itemRepository.save 에서 persist() 호출 => insert into 쿼리문 실행;
         *  But, 이미 존재하는 튜플의 id로 세팅 해주면, 새로운 엔티티 생성이 아닌 이미 존재하는 엔티티의 값을 변경 => itemRepository.save 에서 merge() 호출 => update 쿼리문 실행
         *  변경 감지(Dirty Checking)과 비슷
         *  But,Dirty Checking을 사용하는 것이 더 권장. 
         *  Becuase, dirty checking은 변경하는 값만 세팅해주면 되지만, merge()는 변경하지 않는 값들도 모두 세팅해줘야 합니다. 그렇지 않을 경우, 세팅하지 않는 값은 null로 값이 변함
         *  하지만, 현재 예시에서 item을 변경할때는 모든 속성의 값들이 변경이 될수도 있기 때문에, merge를 사용
         */

        return "redirect:/items";
    }
}
//Item 일부
static public Book createBook(String name, int price, int quantity, String author, String isbn) {
        Book book = new Book();
        book.setName(name);
        book.setPrice(price);
        book.setQuantity(quantity);
        book.setAuthor(author);
        book.setIsbn(isbn);

        return book;
    }
//ItemService 일부
@Transactional  //DEFAULT = false
    public void saveItem(Item item) {
        itemRepository.save(item);
    }
//ItemRepository 일부
public void save(Item item) {
        if (item.getId() == null) {
            em.persist(item);
        } else {
            em.merge(item);
        }
    }

(@RequestMapping, @GetMapping, @PostMapping 등 Spring MVC관련 내용은 이미 학습한 내용으로, 추가로 설명하지는 않겠습니다. https://github.com/k-ms1998/Spring-studies 에서 Spring_MVC1 확인)

준영속 엔티티의 값을 변경할 때는 두가지 방법이 있습니다. 첫번째는 Dirty Checking(변화 감지)이고, 두번째는 EntityManager.merge()를 사용하는 것 입니다.
(준영속 엔티티는 영속성 컨텍스트가 더는 관리하지 않는 엔티티를 뜻합니다.)

1.Dirty Checking: 엔티티의 속성 값중 변화가 발생 했을때, DB에 flush 할때 DB 값과 1차 캐시에 저장된 속성 값들을 비교해서, 변경된 값이 있으면 update 쿼리를 실행해서 DB에 변경된 값을 저장합니다.(더 자세한 내용은 JPA Basics에서 Dirty Checking 포스트 확인)

2. EntityManager.merge(): 속성 값이 변하는 엔티티를 찾아서, 엔티티가 존재하면 속성 값들을 merge()가 호출되는 시점에서 update 쿼리문을 실행합니다.

둘 중에서는 Dirty Checking을 사용하는 것을 추천!
왜냐하면, merge()를 사용할 경우, 엔티티에서 속성 A만 값을 변경하는 경우에, 만약에 속성 B 값을 set 해주지 않으면, 속성 B의 값이 null로 변경되기 때문입니다.
예제로 봤을때, updateItem() 에서는 Book의 속성을 변경시켜주고 있습니다. Book에는 id, name, price, quantity, author, isbn 속성을 가지고 있습니다.
이때, price를 만약에 변경 시킨다고 할때, 새로운 Book 객체를 만들고, 해당 객체에 모든 값들을 set 해줘야합니다(price는 변경된 값으로, 나머지 속성들은 기존 값으로). 그런데, 실수로 name을 set해주지 않으면, name에는 null의 값이 적재되어서 DB에 저장됩니다.
반면에, Dirty Checking에서는, price의 값만 set해줘도 됩니다.
그러므로, 실무에서는 엔티티의 일부 값만 변경을 하기 때문에, dirty checking을 사용하는 것이 더 권장됩니다. (예제에서는 학습 겸, Item의 id 속성 제외하고는 모든 속성이 변경이 가능한 대상이므로 merge() 사용)

좋은 웹페이지 즐겨찾기