Spring Data JPA의 save() 메서드는 Entity가 새로운 객체인지 기존 객체인지 판단 후 INSERT 또는 UPDATE를 결정
🤔 새로운 Entity인지 판단하는 이유?
SimpleJpaRepository의 save() 메서드에서, isNew()를 사용하여 persist를 수행할지 merge를 수행할지 결정함
만약 ID를 직접 지정해주는 경우 신규 엔티티라고 판단하지 않아 merge를 수행함 - 비효율적
- persist(): DB조회 없이 바로 INSERT
- merge(): SELECT 조회 후 INSERT/UPDATE - 느림
JpaEntityInformation의 isNew(T entity)에 의해 판단됨
기본적으로 JpaMetamodelEntityInformation 클래스가 이 역할을 담당
// Spring Data JPA 내부에서 사용되는 인터페이스
public interface JpaEntityInformation<T, ID> {
boolean isNew(T entity);
}
// JpaMetamodelEntityInformation
@Override
public boolean isNew(T entity) {
// @Version 필드 X, @Version 필드가 primitive 타입인 경우
if(versionAttribute.isEmpty() ||
versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
// AbstractEntityInformation의 isNew() 사용
return super.isNew(entity);
}
// @Version 필드가 wrapper 클래스인 경우 - BeanWrapper를 사용해 entity 필드값에 접근
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
// @Version 필드 값이 null 인지 확인
return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}
// AbstractEntityInformation
public boolean isNew(T entity) {
ID id = getId(entity);
Class<ID> idType = getIdType();
if(!idType.isPrimitive()) {
return id == null; // wrapper 타입이면 null 체크
}
if(id instanceOf Number) {
return ((Number) id).longValue() == 0L; // primitive이면 0 체크
}
throw new IllegalArgumentException(String.format("Unsupported primitive id type %s", idType));
}
Case 1: @Version 필드가 wrapper 클래스인 경우
JpaMetamodelEntityInformation의 isNew(T entity) 사용
@Entity
public class User {
@Id
private Long id;
@Version
private Integer version;
}
version 필드가 null 이면 새로운 Entity
Case 2: 나머지 (ID 기반 판단)
- @Version이 없는 경우
- @Version이 primitive 타입인 경우
AbstractEntityInformation의 isNew(T entity) 사용
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
}
- ID가 wrapper 클래스 → id가 null이면 새로운 Enitty
- ID가 primitive 타입 → id가 0이면 새로운 Entity
🤔 키 생성 전략을 사용하지 않고 직접 ID를 할당한 경우?
새로운 Entity로 간주하지 않음. 이때는 엔티티에서 Persistable<T> 인터페이스를 구현해서 JpaPersistableEntityInformation의 isNew()가 동작하도록 해야 함