변경 감지와 병합

Y_Sevin·2022년 1월 12일
0

spring 개념

목록 보기
5/12

변경감지와 병합을 이해하기 위해서는 먼저 준영속 엔티티에 대한 이해가 필요합니다.

JAP는 엔티티를 영속성 컨텍스트에 저장하고 영속상태로 관리합니다. 때문에 영속성 컨텍스트에 존재하는 엔티티의 값을 변경하면 JPA가 트랜젝션 커밋 시점에 변경된 내용을 DB에 반영하는 작업을 수행합니다.

Dirty Checking

JPA를 공부했다면 Dirty Checking이란 용어를 들어봤을 것입니다
JPA는 트랜잭션안에서 커밋된 시점에 flush를 하게 됩니다.
바로 이 시점에 DB의 데이터와 엔티티의 데이터의 변경을 감지해서 Update를 실행해줍니다.

주의사항으로는 Dirty Checking의 대상은 영속 상태이며, 준영속 상태, 비영속 상태는 Dirty Checking의 대상이 아닙니다.

@Transactional
@GetMapping("/")
public ResponseEntity<Users> updateUser() {
    Users user = new Users();
    user.setName("LEE");

    Users saveUser = usersRepository.save(user);
    saveUser.setName("GICHEOL");

    return ResponseEntity.ok(saveUser);
}
curl http://localhost:8080/        
{"id":1,"name":"GICHEOL"}

LEE라는 이름으로 User를 저장하고, GICHEOL이란 이름으로 변경하기만 했습니다.
명시적으로 Update를 호출하진 않았지만, 해당 URL 요청을 하게되면 유저의 이름이 변경된 것을 알 수 있습니다.

준영속 엔티티?

영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말합니다. 즉 이전에는 영속 상태였던 엔티티입니다.

@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.setPrice(form.getPrice());
    book.setStockQuantity(form.getStockQuantity());
    book.setAuthor(form.getAuthor());
    book.setIsbn(form.getIsbn());

    itemService.saveItem(book);
    return "redirect:/items";
}

Book은 영속성 컨텍스트에 관리 대상이 아니지만 관리 대상인 Book의 Id(식별자)가 세팅이 되어있는걸 볼 수있습니다. 즉 데이터베이스에 한번 들어갔다가 나온 객체이기 때문에 JPA가 식별할 수 있는 ID를 가지고 있다는 것을 알 수 있습니다.

때문에 이는 과거에 식별자 기반으로 JPA가 관리를 했다가 현재는 관리하지 않는 엔티티이므로 Book은 준영속 엔티티라고 볼 수 있습니다.

문제점

준영속 엔티티의 문제는 JPA가 관리를 안한다는 것

영속상태의 엔티티는 변경이 일어나면 더티체킹, 즉 변경감지가 일어납니다. 그리고 트랜젝션 커밋 시점에 이를 적용합니다. 하지만 JPA가 관리하지 않으면 아무리 값을 변경해도 DB에 업데이트가 일어나지 않습니다.

그렇다면 이런 준영속 엔티티를 수정할때에는 어떤 방법을 사용해야 할까요?

  • 👍변경 감지 기능 사용
  • 병합(merge) 사용

변경 감지 기능 사용

@Transactional
public void updateItem(Long itenId, Book parm){ //Book parm: 파라미터로 넘어온 준영속 상태의 엔티티
	Item findItem = itemRepository.findOne(itenId);	
    findItem.setId(parm.getId);
    findItem.setName(parm.getName);
}

id기반으로 실제 DB에 있는 영속상태의 엔티티를 findOne을 통해 찾아옵니다.

findItem.setId(parm.getId); 파라미터에서 변경하고자하는 데이터를 영속상태의 엔티티인 findItem에 넣어줍니다.

이렇게 한다면 save를 따로 호출할 필요없이 스프링의 트랜잭션에 의해서 커밋이 되고 jpa는 변경된 사항이 있는지 찾아서 플러시를 날립니다. 그리고 알아서 DB에 UPDATE SQL 을 실행하며 값을 변경하게 됩니다.

병합 사용

병합은 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능입니다.

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

merge란 간단하게 말하자면 변경감지기능에서 사용한 코드를 JPA에서 한줄로 제공한다 생각하면 됩니다.

병합 동작 방식

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

Item mergeItem = em.merge(item);

여기에서 파라미터로 넘어온 item은 영속성을 가지지 않습니다.

mergeItem가 영속성 컨텍스트에서 관리되는 객체입니다.

주의🚨

변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경됩니다. 병합시 값이 없으면 null 로 업데이트 할 위험도 있습니다. (병합은 모든 필드를 교체합니다.)

가장 좋은 해결 방법

불편하더라도 merge 말고 변경 감지 기능 사용을 사용해서 변경합시다..!

  • 엔티티를 변경할 때는 항상 변경 감지를 사용하세요
  • 컨트롤러에서 어설프게 엔티티를 생성하지 마세요.
  • 트랜잭션이 있는 서비스 계층에 식별자( id )와 변경할 데이터를 명확하게 전달하세요.(파라미터 or dto)
  • 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경하세요.
  • 트랜잭션 커밋 시점에 변경 감지가 실행됩니다.

참고
https://leegicheol.github.io/jpa/jpa-dirty-checking-merge/

profile
매일은 아니더라도 꾸준히 올리자는 마음으로 시작하는 개발블로그😎

0개의 댓글