Til
title: 2026-04-16 author: 강병호 (이름) date: 2026-04-16 (날짜) category: TIL/강병호/2026/04 (파일 경로 : TIL/{이름}/{연}/{월}) layout: post (자유) —
Spring Data JPA에서 엔티티의 신규 여부(isNew)를 판단하는 로직은 애플리케이션의 성능과 데이터 정합성에 직결되는 중요한 메커니즘입니다. 핵심은 SimpleJpaRepository의 save() 메서드가 이 판단 결과에 따라 persist()와 merge() 중 어떤 프로세스를 실행할지 결정한다는 점에 있습니다.
1. Spring Data JPA의 기본 판단 전략
별도의 설정이 없다면 Spring Data JPA는 JpaMetamodelEntityInformation을 통해 엔티티의 신규 여부를 판단합니다. 이 과정은 크게 두 가지 기준(@Version 유무와 식별자 상태)에 의해 결정됩니다.
@Version 필드가 존재하는 경우
엔티티 내에 @Version 어노테이션이 적용된 필드가 있다면, 해당 필드의 상태를 최우선으로 확인합니다.
- Wrapper 타입(예: Long, Integer): 필드 값이
null이면 새로운 엔티티로 판단합니다. - Primitive 타입(예: long, int): Primitive 타입은
null이 될 수 없으므로, 이 경우에는 식별자(@Id)를 확인하는 기본 전략으로 넘어갑니다.
@Id 식별자 필드를 확인하는 경우 (AbstractEntityInformation)
@Version 필드가 없거나 primitive 타입일 때 동작하는 기본 로직입니다.
- 객체 타입 식별자:
@Id필드가null이면 새로운 엔티티로 간주합니다. - 숫자형 primitive 식별자:
long이나int타입인 경우, 값이0이면 새로운 엔티티로 간주합니다.
2. 식별자 직접 할당 시 발생하는 문제
@GeneratedValue를 사용하는 경우, 엔티티를 생성한 시점에는 ID가 없다가 persist() 시점에 ID가 생성되므로 isNew()가 정상적으로 true를 반환합니다. 하지만 식별자를 직접 할당(@Id만 사용)하는 경우에는 상황이 달라집니다.
- 사용자가 엔티티 객체를 생성하고 식별자를 직접 세팅합니다.
save()호출 시isNew()를 체크하는데, 이미 ID 값이 존재하므로false를 반환합니다.- Spring Data JPA는 이를 기존에 존재하던 엔티티로 오해하여
persist()가 아닌merge()를 호출합니다. - 성능 저하 발생:
merge()는 해당 데이터가 DB에 있는지 확인하기 위해 먼저SELECT쿼리를 실행합니다. 데이터가 없음을 확인한 후에야INSERT를 수행하므로 불필요한 조회 쿼리가 매번 실행되는 비효율이 발생합니다.
3. Persistable 인터페이스를 통한 해결
직접 할당 방식에서 이러한 비효율을 해결하려면 엔티티가 직접 신규 여부 판단 로직을 갖도록 Persistable<ID> 인터페이스를 구현해야 합니다.
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Item implements Persistable<String> {
@Id
private String id;
@CreatedDate
private LocalDateTime createdDate;
@Override
public String getId() {
return id;
}
@Override
public boolean isNew() {
// 생성일(createdDate)이 null이면 새로운 엔티티로 판단하는 로직이 주로 사용됨
return createdDate == null;
}
}
이렇게 Persistable을 구현하면 JpaPersistableEntityInformation이 동작하며, 엔티티 내부의 isNew() 결과에 따라 save() 메서드가 동작합니다. 보통 @CreatedDate와 같은 감사(Audit) 필드를 활용해 해당 필드가 null인지 여부로 신규 엔티티를 판단하는 방식이 가장 깔끔하고 안정적입니다.
4. 요약
Spring Data JPA의 save()는 만능이 아닙니다. 내부적으로 isNew()를 통해 EntityManager.persist()(신규 저장)와 EntityManager.merge()(병합)를 구분합니다.
- isNew == true:
persist()실행. 영속성 컨텍스트에 바로 저장됩니다. - isNew == false:
merge()실행. DB에서 기존 데이터를 찾는SELECT쿼리가 선행됩니다.
식별자 직접 할당 시 Persistable을 구현하지 않으면 모든 save() 요청마다 불필요한 SELECT 쿼리가 발생하여 시스템 전체의 성능을 떨어뜨리는 원인이 됩니다. 따라서 프로젝트의 식별자 생성 전략에 맞춰 적절한 신규 엔티티 판단 로직을 구축하는 것이 중요합니다.