Dirty Checking vs. EntityManager.merge()


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);
        }
    }
(Spring MVCについては、@RequestMapping、@GetMapping、@PostMappingなどの情報を学習しました.Spring MVCを取得するには、https://github.com/k-ms1998/Spring-studiesを参照してください)
準連続エンティティの値を変更するには、次の2つの方法があります.1つ目はDirty Checking、2つ目はEntity Managerです.merge()を使用します.
(準永続性エンティティとは、永続性コンテキストによって管理されなくなったエンティティです.)
1.Dirty Checking:エンティティの属性値が変更された場合、データベースをリフレッシュするときにプライマリ・キャッシュに格納されている属性値とデータベース値を比較し、変更がある場合はupdateクエリーを実行してデータベースの変更値を保存します.(詳細については、JPA BasicsのDirty Checking Postを参照)
2. EntityManager.merge():属性値が変化するエンティティを検索し、エンティティが存在する場合はmerge()を呼び出したときにupdateクエリー文を実行します.

両者の中ではDirty Checkingがおすすめ!
なぜなら、merge()を使用し、エンティティ内の属性Aの値のみを変更する場合、属性Bの値が設定されていない場合、属性Bの値はnullに変更されるからです.
たとえば、updateItem()はBookのプロパティを変更しています.Bookにはid、name、price、quantity、author、isbn属性があります.
priceを変更する場合は、新しいBookオブジェクトを作成し、そのオブジェクトのすべての値を設定する必要があります(priceは変更された値で、残りの属性は既存の値です).ただし、名前を意図的に設定しないとnullの値がロードされDBに格納されます.
逆に,Dirty Checkingではpriceの値を設定するだけでよい.
したがって、dirty checkingを使用することをお勧めします.実際の操作ではエンティティの一部の値しか変更されません.(例では、itemのid属性を除いてすべての属性が変更可能であるため、merge()を使用します.)