🐱 Meow, meow

Spring Data JPA 를 사용하다보면 save() 메서드 호출 시 내부적으로 persist() 를 호출할지, merge() 를 호출할지 결정하게 된다.
이 결정은 해당 Entity 가 새로운 Entity 인지 여부에 따라 이루어지는데, Spring Data JPA 는 Entity 가 새로운지 어떻게 판단하는지 알아보자.

신규 Entity 판단 방식


Spring Data JPA 는 내부적으로 JpaEntityInformationisNew(T entity) 메서드를 호출해서 판단한다.

@Override
public boolean isNew(T entity) {
    if (versionAttribute.isEmpty()
        || versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
        return super.isNew(entity);
    }
    BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
    return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}

1) @Version 필드가 있다면 → null 여부로 판단
2) @Version 필드가 없다면 → @Id 필드가 null 이거나, primitive 타입인 경우 0인지 확인

🏷️ 즉, ID 가 null 이면 신규 Entity 로 간주하여 persist() 가 호출된다.


직접 ID 를 지정한 경우의 동작


ID 를 직접 지정하면 JPA 는 해당 Entity 가 이미 존재하는 것으로 판단하여 merge() 를 호출한다.
하지만, Database 에는 존재하지 않는 경우 다음과 같은 문제가 발생하는데,

  • SELECT 쿼리로 존재 여부 확인
  • 실제 INSERT 가 아닌 UPDATE 시도
  • → 실패하거나 잘못된 데이터 상태 유발!!

이를 해결하기 위한 방법으로는 Persistable<T> 인터페이스를 구현하면 된다.

// User.class

@Entity
@Table(name = "users")
@EntityListeners(UserEntityListener.class)
public class User implements Persistable<String> {

	@Id
	private String id;

	private String name;

	private boolean isNew = true;

	protected User() {

	}

	public User(String id, String name) {
		this.id = id;
		this.name = name;
	}

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

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return this.name;
	}

	@Override
	public boolean isNew() {
		return this.isNew;
	}

	public void setIsNew(boolean isNew) {
		this.isNew = isNew;
	}

}

// UserEntityListener.class

public class UserEntityListener {
	@PostPersist
	@PostLoad
	public void setNotNew(User user) {
		System.out.println("@PostPersist/@PostLoad called");
		user.setIsNew(false);
	}

}


persist() vs merge()


구분 persist() merge()
동작 새로운 Entity 를 영속성 컨텍스트에 등록 준영속 객체를 병합하여 관리
SELECT 쿼리 ✅ 먼저 조회 후 merge
ID 필요 여부
성능 빠름 (직접 INSERT) 느릴수 있다 (SELECT + UPDATE)
🏷️ 신규 객체를 merge() 로 처리하면 불필요한 SELECT 쿼리가 발생하고 성능 저하 가능성이 존재한다.


신규 Entity 판단이 중요한 이유는 무엇일까?


Spring Data JPA 의 SimpleJpaRepositorysave() 에서 다음과 같이 동작한다.

@Transactional
public <S extends T> S save(S entity) {
    if (entityInformation.isNew(entity)) {
        entityManager.persist(entity); // INSERT
    } else {
        return entityManager.merge(entity); // SELECT → UPDATE
    }
}

ID 를 직접 설정했지만 isNew() 는 false 가 되어, merge() 를 호출하게 되고,
Database 에는 해당 ID 가 존재하지 않지만, 신규 Entity 임에도 불구하고,
SELECTUPDATE 를 하게 되어(Database 조회) 실패 또는 데이터 무결성 오류 가 발생할 수 있고, 비효율적이다.

🏷️ 정확한 isNew() 제어는 성능, 정합성, 쿼리 효율성 측면에서 매우 중요하다.


정리


상황 처리방식
ID 없거나 null → 신규 Entity persist()
ID가 존재하지만 실제 DB 에는 없음 merge() 호출 → 실패 가능성 / 비효율
ID 를 직접 설정한 신규 Entity Persistable<T> + isNew() 로 명시 필요



📗 요약

🖐 Spring Data JPA 는 내부적으로 isNew() 를 통해서 신규 Entity 여부를 판단한다.
🖐 ID 는 존재하지만 실제 DB 에 없는 경우 SELECT + UPDATE 후 merge 를 하므로 정합성이 떨어질 수 있고, 비효율적이다.
🖐 ID 를 직접 설정했을 경우는 Persistable<T> + isNew() 로 명시하는 것이 필요하다.



Reference