Spring Data JPA에서 새로운 Entity를 판단하는 방법?
새로운 Entity인지 판단하는 이유는 SimpleJpaRepository의 save() 메서드에서 isNew()를 사용하여 persist와 merge를 수행할지 결정하는데, 만약 ID를 직접 지정해 주는 경우에는 신규 entity라고 판단하지 않아서 merge를 수행하게 된다. 이때 해당 entity는 신규임에도 불구하고 DB를 조회하기 때문에 비효율적이기 때문에 새로운 entity인지 판단하는 것은 매우 중요하다.
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (this.entityInformation.isNew(entity)) {
this.entityManager.persist(entity);
return entity;
} else {
return (S)this.entityManager.merge(entity);
}
}
- persist() : 영속성 콘텍스트에 새로운 entity로 추가한다. 만약 이미 같은 식별자를 가진 entity가 존재하면 EntityExistsException을 발생시킨다.
- merge() : 이미 영속성 콘텍스트에 존재하는 entity의 상태를 수정하거나, 존재하지 않으면 새로운 entity로 추가한다. 트랜잭션이 commit 되기 전까지는 영속성 콘텍스트에 유지된다.
JpaEntityInformation의 isNew(T entity)에 의해 판단된다. 다른 설정이 없으면 JpaEntityInformation의 구현체 중 JpaMetamodelEntityInformation 클래스가 동작한다.
@Version이 사용된 필드가 없거나, @Version이 사용된 필드가 primitive 타입이면 AbstractEntityInformation의 isNew()를 호출한다.
@Version이 사용된 필드가 wrapperclass이면 null여부를 확인한다.
// JpaMetamodelEntityInformation
public boolean isNew(T entity) {
if (!this.versionAttribute.isEmpty() && !(Boolean)this.versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
return (Boolean)this.versionAttribute.map((it) -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
} else {
return super.isNew(entity);
}
}
// AbstractEntityInformation
public boolean isNew(T entity) {
ID id = (ID)this.getId(entity);
Class<ID> idType = this.getIdType();
if (!idType.isPrimitive()) {
return id == null;
} else if (id instanceof Number) {
Number n = (Number)id;
return n.longValue() == 0L;
} else {
throw new IllegalArgumentException(String.format("Unsupported primitive id type %s", idType));
}
}
@Version이 사용된 필드가 없어서 AbstractEntityInformation 클래스가 동작하면 @Id 어노테이션을 사용한 필드를 확인해서 primitive 타입이 아니라면 null 여부, Number의 하위 타입인지 0인지 확인한다.
@GeneratedValue 어노테이션으로 키 생성 전략을 사용하면 데이터베이스에 저장될 때 id가 할당된다.
만약 직접 ID를 할당하는 경우에는 새로운 entity로 간주되지 않는다. 이때는 entity에서 Persistable<T> 인터페이스를 구현해서 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);
}
public boolean isNew(T entity) {
return entity.isNew();
}
@Nullable
public ID getId(T entity) {
return (ID)entity.getId();
}
}
데이터베이스에 저장되기 전에 메모리에서 생성된 객체는 id가 비어있기 때문에
isNew()는 true가 되어 새로운 entity로 판단한다.🖐️🖐️