이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


이 글은 JPA의 변경 감지와 병합에 대한 이해가 필요합니다.

2024.08.05 - [Java Category/JPA] - [JPA] 병합(Merge)과 변경 감지(Dirty Checking)

 

[JPA] 병합(Merge)과 변경 감지(Dirty Checking)

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.JPA(Java Persistence API)에서 변경 감지(Dirty Checking)와 병합(Merge)은 엔티티 상태 관리를 위한 중요한 개념이

rebugs.tistory.com

 


save() 메서드의 구조

org.springframework.data.jpa.repository.support.SimpleJpaRepository

  • 새로운 엔티티면 저장( persist )
  • 새로운 엔티티가 아니면 병합( merge )

 

save()가 새로운 엔티티를 판단하는 기본 전략

  1. 식별자가 객체일 때 null 로 판단
  2. 식별자가 자바 기본 타입일 때 0 으로 판단
  3. Persistable 인터페이스를 구현해서 판단 로직 변경 가능

 

식별자가 객체일 때 null로 판단

엔티티의 기본키가 자바의 기본 타입이 아니라 String 등이고, 식별자(기본키)가 null 이면 영속성 컨텍스트에서 관리하지 않는 새로운 객체라고 판단한다.

이 때, 주의를 해야하는 점이 있다.

데이터베이스에서 자동으로 식별자를 지정해주는 전략이 아니라 애플리케이션에서 자체적으로 식별자를 지정해주는 전략이라면 새로운 엔티티가 생성될 때, persist가 아니라 merge가 작동하게 된다.

따라서 영속성 컨텍스트는 데이터베이스에서 데이터를 조회하게 되고, DB에 데이터가 없다는 것을 뒤 늦게 알고 그제서야 새로운 객체로 판단하게 된다.

 

 JPA 식별자 생성 전략이 @Id 만 사용해서 직접 할당이면 이미 식별자 값이 있는 상태로 save() 를 호출한다.

따라서 이 경우 merge() 가 호출된다. merge() 는 우선 DB를 호출해서 값 을 확인하고, DB에 값이 없으면 새로운 엔티티로 인지하므로 매우 비효율적이다.


따라서 Persistable 를 사용해서 새로운 엔티티 확인 여부를 직접 구현하게는 효과적이다.

 

식별자가 자바 기본 타입일 때 0으로 판단

int 나 long 등 자바의 기본 타입이라면 초기화를 하지 않았다면 기본적으로 0으로 세팅되어있다.

따라서 엔티티의 식별자가 0이면 영속성 컨텍스트에서 관리하지 않는 새로운 객체라고 판단한다.

@Entity
public class Item {

    @Id @GeneratedValue
    private Long id;
}

 

식별자 생성 전략이 @GenerateValue 면 save() 호출 시점에 식별자가 없으므로 새로운 엔티티 로 인식해서 정상 동작

 

 

Persistable 인터페이스를 구현

등록시간( @CreatedDate )을 조합해서 사용하면 이 필드로 새로운 엔티티 여부를 편리하게 확인할 수 있다.

(@CreatedDate에 값이 없으면 새로운 엔티티로 판단)

 

Persistable 인터페이스

package org.springframework.data.domain;
public interface Persistable<ID> {
    ID getId();
    boolean isNew();
}

 

Persistable 구현

@Entity
@EntityListeners(AuditingEntityListener.class)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Item implements Persistable<String> {

    @Id
    private String id;

    @CreatedDate
    private LocalDateTime createdDate;

    public Item(String id) {
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public boolean isNew() {
        return createdDate == null; //생성 시간이 null이면 새로운 객체로 판단하도록 유도
    }
}