DB/JPA

[JPA] 준영속(Detached) 상태 엔티티 수정하는 방법

꾸준함. 2023. 5. 7. 22:03

개요

약 2년 전에 영속성 컨텍스트에 대해 정리했었습니다.

https://jaimemin.tistory.com/1898

 

[JPA] PersistenceContext 간단 정리

개요 이번 게시글에서는 JPA 내부 구조 및 동작 방식을 파악하는데 중요한 개념인 PersistenceContext(영속성 컨텍스트)에 대해 알아보겠습니다. 1. EntityManagerFactory & EntityManager JPA 내부 구조를 살펴보

jaimemin.tistory.com

 

이후로는 주로 MyBatis가 적용된 프로젝트를 개발하여 JPA에 소홀하다가 최근에 JPA를 적용한 프로젝트를 개발하게 되어 복습을 진행하고 있습니다.

이번 게시글에서는 준영속 상태인 엔티티와 관련하여 중요한 개념인 변경 감지와 병합에 대해 정리해 보겠습니다.

이해를 돕기 위해 위 게시물을 먼저 읽고 오시는 것을 추천드립니다.

 

준영속 엔티티란?

수정하는 방법을 소개하기에 앞서 우선 준영속 엔티티에 대한 정의가 필요합니다.

앞선 게시글에서는 준영속 엔티티를 아래와 같이 정의했습니다.

  • 준영속 상태는 EntityManger에서 분리되어 있는 상태
  • EntityManager가 제공하는 기능을 사용 못함

 

좀 더 자세히 설명하자면 준영속 엔티티는 데이터베이스에 한 번 갔다 온 엔티티로 더 이상 JPA가 관리를 하지 않는 엔티티이며 DB에 한 번 저장되었기 때문에 식별자가 존재합니다.

따라서 트랜잭션 내 해당 엔티티에 setter 메서드를 통해 필드를 수정하더라도 Dirty Checking을 통한 업데이트가 진행되지 않습니다.

 

사실 글로만 봐서는 이해하기가 까다로운 개념이므로 Spring MVC 예시를 들어보겠습니다.


 

  • Get 메서드를 통해 수정하는 폼을 제공하고
  • Post 메서드를 통해 수정된 내용을 DB에 반영을 하는 코드입니다.

 

여기서 convertExample() 메서드를 통해 반환받는 Example이 영속 엔티티라면 ExampleService의 save() 메서드를 명시적으로 호출하지 않더라도 Dirty Checking 기능을 통해 수정 사항이 바로 반영이 되었을 것입니다.

하지만 Example 객체는 준영속 상태이기 때문에 명시적으로 save() 메서드를 호출해야 DB에 반영이 됩니다.

Example 객체가 준영속 상태인 이유는 ObjectMapper의 convertValue() 메서드를 통해 ExampleForm의 id 값을 갖고 있으므로 식별자가 존재하기 때문입니다.

 

준영속 엔티티 수정하는 방법

준영속 엔티티를 수정하는 방법은 아래와 같이 두 가지 방법이 있습니다.

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

 

A. 변경 감지 기능 사용

 

변경 감지 기능은 준영속 엔티티가 가지고 있는 식별자를 통해 DB로부터 같은 엔티티를 조회한 뒤 해당 엔티티의 데이터를 수정하는 방법입니다.

 

 

 

기존 예시에서 PostMapping 메서드에서 ExampleService의 update() 메서드를 호출할 경우 변경 감지 기능을 통해 준영속 엔티티가 업데이트가 되는 것을 확인할 수 있습니다.

정리하자면 변경 감지 기능은 아래와 같이 동작합니다.

  • 준영속 엔티티의 식별자를 통해 영속성 컨텍스트에서 엔티티를 다시 조회한 후
  • 트랜잭션 커밋 시점에 변경 감지(Dirty Checking)를 통해 데이터를 수정

 

B. 병합 사용

 

병합 사용 방법은 EntityManager에서 제공하는 merge() 메서드를 사용하며 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능입니다.

병합 동작 방식의 과정은 아래와 같습니다.

  • EntityManager의 merge() 메서드 호출
  • 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회
  • 조회환 영속 엔티티에 준영속 엔티티의 값을 채워 넣음
  • 영속 상태인 엔티티를 반환

 

코드로 위 과정을 표현하자면 아래와 같습니다.


 

여기까지만 보면 변경 감지 기능과 병합 둘 다 사용해도 별 문제없는 기능처럼 보입니다.

하지만 병합 사용에는 치명적인 단점이 존재합니다.

 

병합 사용의 단점

EntityManager의 merge 메서드를 보면 아래와 같습니다.

 

 

사진에서도 보면 알 수 있다시피 EntityManager의 merge 메서드는 준영속 엔티티의 식별자만 전달하는 것이 아니라 엔티티 자체를 전달합니다.

즉, 특정 필드만 업데이트하는 것이 아니라 객체 내 전체 필드를 업데이트하는 방식입니다.

실무에서는 보통 업데이트 기능을 매우 제한적으로 제공하여 특정 필드만 수정 가능하도록 하는 방면 merge 메서드의 경우 모든 필드를 변경해 버리고 넘겨주는 준영속 엔티티의 필드 중 데이터가 없는 것들은 null로 업데이트를 시켜버리는 치명적인 단점이 존재합니다.

해결책으로는 수정하는 화면에서 모든 데이터를 항상 유지하는 방법이 있지만 해당 방법은 엔티티의 변경이 발생할 때 백앤드 코드뿐만 아니라 화면 코드까지 수정해야 하기 때문에 추천하기 어려운 방법입니다.

따라서 준영속 엔티티를 수정할 때는 변경 감지 기능을 사용하는 것을 추천드립니다.

 

참고

실전! 스프링 부트와 JPA 활용 1 (김영한 강사님)

반응형

'DB > JPA' 카테고리의 다른 글

[JPA] Hibernate MultipleBagFetchException  (0) 2023.06.28
[JPA] JPQL 추가 정리  (0) 2021.10.18
[JPA] JPQL 간단 정리  (0) 2021.10.16
[JPA] 값 타입 정리  (0) 2021.09.29
[JPA] 프록시와 연관관계 관리 정리  (0) 2021.09.14