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() 메서드를 호출한다.
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를 반환한다.
키 생성 전략을 사용하지 않고 직접 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()의 구현에 따라 새로운 엔티티 여부가 결정되며, 이를 통해 보다 명시적인 제어가 가능해진다.
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 인터페이스를 구현하여 개발자가 직접 엔티티의 상태를 관리할 수 있도록 한다.