JPA를 공부하는 과정에서 여러 의문점이 들기 시작한다.
예를 들어 컬렉션을 포함하는 엔티티가 있는데 해당 엔티티의 컬렉션의 내용이 변경되어 엔티티에 변경된 컬렉션을 넣었다.
이 과정에서 JPA는 변경 감지, 병합(merge) 둘 중 하나로 처리하여 데이터베이스에 쿼리를 전송하여야 하는데,
변경되어 없어진 정보들은 JPA가 어떻게 처리하는 가에 대해서다.
상상해보자면 기존의 스냅숏에 저장된 컬렉션과 현재 저장된 컬렉션을 비교하여 제거된 엔티티들을 파악하고 해당 엔티티의 PK값을 통해 FK값을 자동적으로 지워 연관관계를 끊어버리는가?
JPA를 공부할 수 록 이해가 잘 안 되는 부분들이 상당히 많다.
중요 논점
변경된 엔티티를 데이터베이스 테이블에 적용하기 위해 JPA는 어떤 방법을 사용하고
그 방법의 개념은 무엇이고 해당 방법들이 적용되어 어떠한 쿼리들이 전송될 수 있는지가 이번 포스팅의 목적이다.
우선 JPA가 Entity의 수정을 어떤 방법으로 사용하는지 알아보아야 한다.
변경 감지와 병합(merge)
* 준영속 엔티티
- 영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다.
새로운 엔티티를 만들어도 해당 엔티티가 기존에 존재하는 식별자를 가지고 있으면 준영속 엔티티라고 볼 수 있다.
우리가 JPA를 통해 기본적으로 엔티티를 변경하고 저장하기 위한 방법은 다음과 같다.
@Test
@Transactional
public void updateEntity() throws Exception {
Member member = entityManager.find(Member.class, 1L);
member.setName("이름 변경");
}
엔티티 매니저에서 엔티티를 찾고 엔티티를 변경하고 트랜잭션이 commit 되어 종료되는 순간 JPA는 update 쿼리를 자동적으로 데이터베이스에 전송한다.
이걸 변경감지 (dirty checking)이라고 한다.
해당 과정을 자세히 보자.
- 트랜잭션이 시작된다.
- Entity Manager를 통해 영속성 엔티티를 찾아온다.
- 이후 엔티티의 속성을 변경을 한다.
- 트랜잭션이 종료되면서 정상 동작이기에 commit 한다.
- JPA는 flush()를 한다.
- flush는 영속성 콘텍스트에서 변경된 엔티티를 감지하여 update 쿼리를 데이터베이스에 전달하는 과정이다.
변경 감지 말고 또 다른 방법이 존재하는데 이를 병합(merge)라고 한다.
병합이라는 것은 준영속 상태의 엔티티를 영속 상태로 변경할 때 사용하는 기능이다.
이는 기존의 관리하던 엔티티의 내용을 병합 파라미터로 넘어온 엔티티 자체로 변경해버린다.
@Test
@Transactional
public void updateEntity() throws Exception {
Member member = entityManager.find(Member.class, 1L);
Member updateMember = new Member();
updateMember.setId(member.getId());
updateMember.setName("new name");
entityManager.merge(updateMember);
}
새로운 Member객체를 생성하고 영속성 콘텍스트에서 찾아온 엔티티의 PK값을 설정한 이후 별도의 입력 값으로 updateMember를 초기화한다.
영속성 컨텍스트에서 찾아온 식별자 값을 설정함으로써 updateMember는 새로운 객체지만 준영속 엔티티가 된다.
이제 준영속 엔티티를 영속 엔티티로 변경하기 위해 merge를 사용하여 데이터를 변경한다.
commit이후 변경된 데이터가 반영된다.
이 두 개의 방법의 차이는 무엇일까?
병합 동작 방식
- merge() 호출
- 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.
만약 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고 , 1차 캐시에 저장한다. - 1번 과정에서 조회한 엔티티는 영속성 엔티티이다. 영속성 엔티티에 파라미터로 넘어온 준영속 엔티티 데이터를 밀어 넣는다.
- 이후 영속 상태인 엔티티를 반환한다.
이를 코드상으로 보면 다음과 같다.
@Test
@Transactional
public void updateEntity() throws Exception {
Member member = entityManager.find(Member.class, 1L);
//준영속 엔티티 생성
Member updateMember = new Member();
updateMember.setId(member.getId());
updateMember.setName("new name");
//Id를 통해 찾아온 새로운 영속성 엔티티에 updateMember 값을 밀어 넣고
//mergeMember라는 새로운 영속 엔티티 반환
Member mergeMember = entityManager.merge(updateMember);
}
이러한 병합 방법은 변경 감지와 차이점이 존재한다.
변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다.
만약 병합 시 값이 없으면 null로 업데이트할 위험이 있다.
그럼 변경 감지를 사용하여 엔티티에 변경을 가하는 것이 최고의 선택이다.
이때 주의사항은 다음과 같다.
엔티티의 변경에 대해 도메인이 직접 처리할 수 있는 의미 있는 메서드가 필요하다.
엔티티의 생성을 여러 계층에서 어설프게 생성하면 코드 블록이 매우 커질 뿐만 아니라 생성되는 시점을 디버깅하기도 매우 어려워진다. 이를 엔티티가 관리할 수 있도록 생성 지점을 줄여 놓는 것이 좋아 보인다.
변경될 데이터와 확실한 엔티티의 ID를 전달하고 트랜잭션이 있는 서비스 계층에서 조회하고 변경하자.
JPA가 엔티티를 어떻게 변경하는지 두 개의 방법과 그에 따른 차이점, 변경을 위해 주의하여 설계하여야 하는 부분에 대해 알아보았다.
하지만 아직 내 궁금증은 풀리지 않았다. 연관된 엔티티의 변경에 대해 알아보기 위해 다음에는 Cascade에 대해 알아보아야 한다.
'데이터 접근 기술 > JPA' 카테고리의 다른 글
주말 정리 (0) | 2022.10.24 |
---|---|
영속성 컨텍스트 (0) | 2022.10.23 |
JPA 공부하기 전 (0) | 2022.10.22 |
[JPA] 공부하면서 나중에 찾아볼 것들 (0) | 2021.04.17 |
[JPA] 2차 캐시 (0) | 2021.04.17 |
댓글