ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA 변경 감지와 병합(merge)
    JPA 2021. 12. 22. 16:46

    JPA 변경 감지(dirty checking)

    기본적으로 변경이 일어나면 database에 commit을 해준다.

    //JPA 변경 감지 Test
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ItemUpdateTest {
    
        @Autowired
        EntityManager em;
    
        @Test
        public void updateTest() throws Exception
        {
            Book book = em.find(Book.class, 1L);
    
            //Transaction
            book.setName("changeName");    //데이터 변경
    
            //변경 감지 == dirty checking
            //TX commit
    
        }
    }

    준영속 엔티티

    영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다.
    즉, DB에 한번 저장되어서 식별자(id)가 존재하는 객체(엔티티)를 말한다.
    JPA가 관리하지 않는다. 이에 대한 해결 방안이 있다. 변경 감지 기능 사용, 병합(merge) 사용

    //준영속 엔티티는 Book 객체
     @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";
        }

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

    변경 감지 기능 사용(권장하는 방법)

    영속성 컨텍스트에서 엔티티를 다시 조회한 후에 변경할 값 선택하고 데이터를 수정하는 방법
    트랜잭션 커밋 시점에 변경 감지(Dirty Checking)이 동작해서 데이터베이스에 UPDATE SQL 실행

    //ItemService.java
    //영속성 컨텍스트가 자동 생성
    //변경 감지 기능 예시
    @Transactional
        public void updateItem(Long itemId, String name, int price){
            Item item = itemrepository.findOne(itemId); //영속성 엔티티
    
            item.setName(name);
            item.setPrice(price);
        }
    //ItemController.java
       @PostMapping("/items/{itemId}/edit")
        public String updateItem(@PathVariable String itemId, @ModelAttribute("form") BookForm form){
            // 변경 감지를 이용한 상품 수정
            //상품 수정 권장 코드
            itemService.updateItem(form.getId(), form.getName(), form.getPrice());
            return "redirect:/items";
        }

    병합(merge) 사용(권장되지 않는 방법)

    준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능

    병합시 동작 정리

    1. 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다.
    2. 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체한다(병합한다).
    3. 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행된다.

    📌주의
    병합을 사용하면 모든 속성이 변경된다. 병합시 값이 없으면 null로 업데이트할 위험도 있다.(병합은 모든 필드를 교체한다.)

    //병합 예시
    @Transactional
    void update(Item itemParam) //itemParam : 파라미터로 넘어온 준영속 상태의 엔티티
        Item mergeItem = em.merge(item);
    //merge 예시
    public void save(Item item){
            if(item.getId() == null){
                em.persist(item);   //식별자가 없으므로 새로 저장
            } else {
                em.merge(item);     //식별자가 있으면 병합 
            }
        }

    이렇게 보면 쉽게 이해되지 않을 수 있다. 왜 Item은 save 메소드에 위와 같은 기능을 줄까? member 엔티티도 save 메서드가 있었지만 위와 같은 로직은 짜지 않았었는데, 둘의 차이점이 무엇일까? 거기에 답은 수정 기능에 있다. member의 경우 비즈니스 설계상 수정 기능을 추가하지 않았기에 persist만 해주면 됐었는데, Item의 경우는 수정 기능이 있기 때문에 DB에서 직접 가져와서 수정을 해준 뒤 다시 저장을 해줘야 한다. 즉, 수정 후 저장을 하게 되면 id는 null이 아니기 때문에 merge(병합) 기능을 사용하는 것이고, 이외의 경우에는 persist만 해주면 된다.

    정리✔

    가급적이면 merge를 사용하지 않고, 변경 감지를 사용하는것이 좋다.
    직접 set메서드를 생성해서 다른 클래스에서 호출하는 방식이 가장 이상적이다.
    또한 merge는 모든 필드 값이 변경되므로 null값에 위험하다.
    컨트롤러에서 엔티티를 어설프게 만드는것보단 트랜잭션이 있는 서비스 계층에 식별자와 변경할 데이터를 명확하게 전달하는것이 좋다.
    (파라미터 or dto)

    참고 : 인프런 김영한 강사님(JPA 강의 활용 1편)📌

    'JPA' 카테고리의 다른 글

    save  (0) 2023.01.30
    JPA @Transactional과 @Rollback  (0) 2022.01.04
    JPA flush()와 clear()  (0) 2021.12.27
    JPA를 이용한 동적 쿼리 선언  (0) 2021.12.20
Designed by Tistory.