[JPA] 준영속 상태와 변경 감지

Jaeyoo (유재형)·2022년 3월 7일
1
post-thumbnail

준영속 상태


준영속 상태란?

  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된것을 준영속 상태라고한다.
  • 영속성 컨텍스트가 제공하는 기능을 사용하지 못함

준영속 상태 만드는 법

  • em.detach(entity)
    • 특정 엔티티만 준영속 상태로 전환
    • 1차 캐시에서 빠진다라고 생각하면 쉬움
  • em.clear()
    • 영속성 컨텍스트 완전히 초기화
    • 테스트 케이스 작성시 사용
  • em.close()
    • 영속성 컨텍스트를 종료

준영속 엔티티


준영속 엔티티란?

: 영속성 컨텍스트가 더는 관리하지 않는 엔티티를 뜻한다.

  • DB에 한번 갔다온 엔티티

  • DB에 한번 저장되어 식별자가 존재하는 객체

    이러한 준영속 엔티티는 JPA가 관리를 하지않기때문에 객체를 수정을 해도 DB에 Update가 일어나지 않는다.
    준영속 엔티티가 무엇인지 감이 잘 안잡힐수있다. 예시를 통해 보자

예시

Form 태그로 데이터 수정

//Controller
//1
@GetMapping("items/{itemId}/edit")
public String updateItemForm(@PathVariable("itemId") Long itemId, Model model){
    Book item = (Book) itemService.findOne(itemId);

    BookForm form = new BookForm();
    form.setId(item.getId());
    form.setName(item.getName());
    ...
    form.setIsbn(item.getIsbn());

    model.addAttribute("form", form);
    return "items/updateItemForm";
}

//2
@PostMapping(value = "/items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form) {
    Book book = new Book();
    book.setId(form.getId());
    book.setName(form.getName());
    ...
    book.setIsbn(form.getIsbn());
    
    itemService.saveItem(book);
    return "redirect:/items";
<!--updateItemForm.html-->
<form th:object="${form}" method="post">
        <input type="hidden" th:field="*{id}" />
        <div class="form-group">
            <label th:for="name">상품명</label>
            <input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력하세요" />
        </div>
        ...

예시 설명

  1. items/{itemId}/edit 으로 접근하면 컨트롤러는 영속성 엔티티를 찾아 BookForm 이라는 DTO에 그 값을 넣고 모델을 통해 뷰템플릿에 전달
  2. updateItemForm.html 에서 전달받은 form 객체를 HTML의 Form 태그를 통해 수정
  3. controller에서는 post 요청을 받아 book 객체를 생성하고 HTML에서 form을 통해 받은 데이터로 book 객체를 수정하고 저장한다.

질문 : 2번 updateItem 에서 book 객체의 수정사항이 변경감지(Dirty Checking)가 될까❓

➡️ 변경감지가 일어나지않는다.

위와 같은 예시에서는 변경감지가 일어나지 않는다. 왜냐하면 book객체는 준영속 엔티티 이기때문이다.

준영속 엔티티의 핵심은 식별자를 기준으로 영속상태가 되어 DB에 저장된적이 있는가 이다.
book객체는 이미 영속엔티티였던 form의 id값을 book.setId(form.getId()) 하였으므로 준영속 엔티티인것이다.


준영속 엔티티 수정 방법


이러한 준영속 엔티티를 수정하는 두가지 방법이있다.

  • 변경 감지 기능 사용
  • 병합 사용

1) 변경 감지 기능 사용 (Dirty Checking)

service 계층게 메서드를 추가한다.

	@Transactional
	void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
		Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한다.
		findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다. 
	}
  • 영속성 컨테스트에서 해당 id를 다시 조회한후 데이터를 수정한다.
  • 조회한 findItem은 영속상태이다.
  • 이러한 영속상태의 데이터를 수정하면 커밋 시점에 내부적으로 flush가 발생해 Dirty Checking이 일어난다.

즉 변경감지기능 사용은 준영속 엔티티의 id로 해당 영속엔티티를 불러와 수정하는것이다.

2) 병합 사용

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

과정설명

  1. merge(itemParam)를 실행
  2. 파라미터로 넘어온 준영속 엔티티의 식별자로 1차캐시에 조회
    2-1. 만약 1차 캐시에 없다면 DB에 조회후 1차캐시에 저장
  3. 조회한 영속 엔티티(item)를 준영속 엔티티(itemParam) 값으로 수정한다.
  4. 영속상태인 mergeItem을 반환한다.

코드로 표현을 해보자면 merge를 사용시 아래코드와 같은 일을 한다.

  @Transactional
  public Item update(Long itemId, Item itemParam) {
  		Item findItem = itemRepository.findOne(itemId);
      	findItem.setName(itemParam.getName());
        ...
        return findItem;
  }

병합의 단점

하는 일은 병합과 변경 감지 기능이 비슷한데 병합에는 단점이 있다.
em.merge(item) 이런식으로 파라미터에 객체를 통채로 보낸다. 그러므로 특정 속성을 변경 할 수 없고 객체의 모든 속성을 변경해야한다.

모든 속성을 변경하는게 왜 단점이냐 라고 질문할수있는데 예시를 들어보겠다.

만약 item의 가격을 수정하고싶지않을 경우

  • 변경 감지는 속성값 하나하나 수정하기때문에 가격 부분을 제외하고 수정하면된다.
  • 병합(merge)를 사용할 경우 객체 자체를 넘기기때문에 객체에 price가 null이면 수정되서 반환된 객체의 price도 null로 수정되어 문제가 발생할수있다.

정리

: 준영속 엔티티를 변경할때는 항상 변경 감지를 사용해라

  • 컨트롤러에서 어설프게 엔티티 생성 금지
  • 컨트롤러에서는 파라미터나 DTO를 이용해 트랜잭션이 있는 서비스 계층에 데이터를 전달해라
  • 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고 엔티티의 데이터를 직접 변경해라
  • 트랜잭션 커밋 시점에 변경 감지 실행된다.
profile
기록과 반복

0개의 댓글