Spring Data JPA에서 새로운 Entity 판별 방법

시훈·2025년 3월 14일

1. 새로운 Entity 판별 로직의 핵심

Spring Data JPA는 새로운 Entity 여부를 판단하기 위해 JpaEntityInformation 인터페이스의 isNew(T entity) 메서드를 사용한다. 기본 구현체인 JpaMetamodelEntityInformation은 아래와 같이 동작한다.

@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);
}

위 코드에서 @Version 애노테이션이 적용된 필드가 있을 경우, 해당 필드가 래퍼 클래스(wrapper class)인지 primitive 타입인지를 확인하며, 값이 null이면 새로운 엔티티로 판단한다. 또한, @Version 필드가 없거나 primitive 타입인 경우에는 상위 클래스(AbstractEntityInformation)의 isNew() 메서드를 호출한다.

2. AbstractEntityInformation의 isNew() 구현

AbstractEntityInformation 클래스는 엔티티의 ID 값을 통해 새로운 엔티티 여부를 판단한다. 아래 코드를 통해 그 동작 방식을 살펴본다.

public boolean isNew(T entity) {

    Id id = getId(entity);
    Class<ID> idType = getIdType();

    if (!idType.isPrimitive()) {
        return id == null;
    }

    if (id instanceof Number) {
        return ((Number) id).longValue() == 0L;
    }

    throw new IllegalArgumentException(String.format("Unsupported primitive id type %s", idType));
} 

ID 필드가 primitive 타입이 아닐 경우:
단순히 ID 값이 null인지를 확인한다.

ID 필드가 Number의 하위 타입인 경우:
값이 0L이면 새로운 엔티티로 판단한다.

그 외의 primitive 타입:
지원하지 않는 primitive 타입이라면 예외를 발생시킨다.

또한, @GeneratedValue 애노테이션을 사용하여 키 생성 전략을 적용하는 경우, 엔티티가 DB에 저장되기 전에는 ID가 할당되지 않으므로 isNew()는 true를 반환한다.

3. 직접 ID 할당 시의 동작 방식

키 생성 전략을 사용하지 않고 직접 ID를 할당하는 경우에는 기본적으로 새로운 엔티티로 판단되지 않는다. 이 경우 엔티티는 Spring Data JPA의 Persistable 인터페이스를 구현해야 한다. 이를 통해 JpaMetamodelEntityInformation 대신 JpaPersistableEntityInformation의 isNew()가 호출된다.

public class JpaPersistableEntityInformation<T extends Persistable<ID, ID>> 
        extends JpaMetamodelEntityInformation<T, ID> {

    public JpaPersistableEntityInformation(Class<T> domainClass, Metamodel metamodel, 
            PersistenceUnitUtil persistenceUnitUtil) {
        super(domainClass, metamodel, persistenceUnitUtil);
    }

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

    @Nullable
    @Override
    public ID getId(T entity) {
        return entity.getId();
    }
}

즉, 엔티티가 직접 ID를 할당받는 경우에는 entity.isNew()의 구현에 따라 새로운 엔티티 여부가 결정되며, 이를 통해 보다 명시적인 제어가 가능해진다.

4. 새로운 Entity 판별의 중요성

SimpleJpaRepository의 save() 메서드는 아래와 같이 동작한다.

@Override
@Transactional
public <S extends T> S save(S entity) {

    Assert.notNull(entity, "Entity must not be null");

    if (entityInformation.isNew(entity)) {
        entityManager.persist(entity);
        return entity;
    } else {
        return entityManager.merge(entity);
    }
}

위 코드에서 isNew()의 결과에 따라 persist 또는 merge를 호출한다.

새로운 엔티티인 경우:
persist를 호출하여 영속성 컨텍스트에 추가하고, 이후 DB에 INSERT를 수행한다.

이미 존재하는 엔티티인 경우:
merge를 호출하여 DB에서 해당 엔티티를 조회한 후 업데이트를 수행한다.

만약 직접 ID를 할당하는 상황에서 엔티티가 신규임에도 불구하고 isNew()가 false를 반환하면, 불필요한 DB 조회가 발생하여 성능 저하로 이어질 수 있다. 따라서 엔티티의 새로운 여부를 올바르게 판단하는 것은 애플리케이션의 성능 및 데이터 무결성을 유지하는 데 매우 중요하다.

결론

Spring Data JPA는 엔티티의 새로운 여부를 판단하기 위해 다양한 조건과 메커니즘을 사용한다.

@Version 필드의 유무 및 타입에 따라 동작이 달라지며,
ID 필드의 타입에 따라 단순한 null 체크 또는 0 값 확인을 통해 신규 여부를 판단한다.
직접 ID를 할당하는 경우에는 Persistable 인터페이스를 구현하여 개발자가 직접 엔티티의 상태를 관리할 수 있도록 한다.

profile
Backend Developer / Cloud Engineer

0개의 댓글