Spring Data

JPA 변경감지와 병합

rohyun 2021. 11. 3. 23:51

JPA 변경감지와 병합


준영속 엔티티란?

@PostMapping("items/{itemId}/edit")
    public String updateItem(@ModelAttribute("form") BookForm form, @PathVariable String itemId) {
        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 에 한 번 저장되어서 식별자가 존재한다. 따라서 new 를 통해 새로 만들어낸 엔티티도 기존의 식별자를 가지고 있다면 준영속 엔티티로 볼 수 있다.

준영속 엔티티를 수정하는 두 가지 방법

  • 변경감지 기능 사용 ( dirty checking ) - best practice

  • @Transactional void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티 Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한다. 이때 조회함으로해서 해당 엔티티 객체는 persist 상태가 된다. findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다. 객체는 persist 상태이기 때문에 save 메소드를 호출하지 않아도 변경이 감지되어 트랜잭션 커밋 시점에 업데이트 쿼리가 날아간다. }

  • 병합 사용 ( merge )

    jpamerge

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

  • 병합의 동작 방식

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

변경 감지가 병합에 비해 best - practice 인 이유?

  • 변경 감지 기능을 사용하면 언하는 속섬안 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다. 따라서 병합시 값이 없다면 null 로 업데이트 될 위험이 존재한다. ( 병합은 모든 필드를 교체하므로 )
  • 실무에서는 보통 업데이트 기능이 매우 재한적이다. 그런데 병합은 모든 필드를 변경해버리고, 데이터가 없으면 null로 업데이트 해버린다. 병합을 사용하면서 이 문제를 해결하려면, 변경 폼 화면에서 모든 데이터를 항상 유지해야 한다. 실무에서는 보통 변경가능한 데이터만 노출하기 때문에, 병합을 사용하는 것이 오히려 번거롭다.

가장 좋은 해결 방법 - 엔티티를 변경할 때에는 항상 변경 감지를 사용하자 !

  • 컨트롤러에서 어설프게 엔티티를 생성해서는 안된다.
  • 트랜잭션이 있는 서비스 계층에 식별자( id )와 변경할 데이터를 명확하게 전달해야 한다. ( 파라미터 or DTO )
  • 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경해야 한다.
  • 따라서 트랜잭션이 커밋되는 시점에 변경 감지가 실행된다.
 /**
 * 상품 수정, 권장 코드
 */
 @PostMapping(value = "/items/{itemId}/edit")
 public String updateItem(@ModelAttribute("form") BookForm form) {
 itemService.updateItem(form.getId(), form.getName(), form.getPrice());
 return "redirect:/items";
 }
}
 /**
 * 영속성 컨텍스트가 자동 변경
 */
 @Transactional
 public void updateItem(Long id, String name, int price) {
 Item item = itemRepository.findOne(id);
 // item.setName(name); // setter 도 사용 비추. setter 대신 도메인 단에 명시적인 메소드를 만들어서 사용하자.
 // item.setPrice(price);
 item.change(name, price) // 추적 가능성을 높이기 위한 도메인 단 엔티티 변경 메소드
 }

데이터를 수정할 때에는 Spring Data JPA 의 save() 메소드를 함부로 사용하지 않아야겠다 !

REFERENCES

김영한님 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발

'Spring Data' 카테고리의 다른 글

Querydsl 초급 문법  (0) 2021.11.04
JPA Open Session In View  (0) 2021.11.03
JPA Proxy  (0) 2021.11.03
연관관계 주인과 mappedBy  (0) 2021.11.03
@OneToOne, @ManyToMany  (0) 2021.11.03