매일메일 이라는 서비스가,
매일 오전 09시 기술면접 질문을 보내준다길래 구독을 신청해봤다.
질문의 답이 무엇인지 공부한 기록을 시간 되는대로 기록해봐야지.
지금은 BE/FE 두 직군에 대해서만 질문을 제공하는듯?
Spring Data JPA에서 새로운 Entity인지 판단하는 방법은 무엇일까요?
일단 하나도 몰라서 차근차근히 접근해봤다.
이를 보기위해선 JpaRepository
의 기본 구현인 SimpleJpaRepository
의 save()
를 봐야하는데,
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
...
@Override //line 620
@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);
}
}
entityInformation.isNew(entity)
를 기반으로 판정하는걸 볼 수 있었다.
새로운 엔티티면, entityManager.persist(entity)
INSERT 를,
기존의 엔티티면, entitiyManager.merge(entity)
UPDATE 를 수행하는걸 볼 수 있었다.
그렇다면, isNew
는 어떻게 판정하는걸까?
조건이 되는 EntityInformaion.isNew()
를 찾아보면
public abstract class AbstractEntityInformation<T, ID> implements EntityInformation<T, ID> {
...
@Override // line 42
public boolean isNew(T entity) {
ID id = getId(entity);
Class<ID> idType = getIdType();
if (!idType.isPrimitive()) {
return id == null;
}
if (id instanceof Number n) {
return n.longValue() == 0L;
}
throw new IllegalArgumentException(String.format("Unsupported primitive id type %s", idType));
}
기본형 id type 에 대해서는, 해당 타입을 ID 로 든 엔티티가 신규인지 아닌지 여부를 판정하는 로직을 제공해주는걸 볼 수 있었다.
기본형 타입을 가지며, null 이라면 신규
id type 이 숫자이며 값이 0 이라면 신규.
그 외 기본형은 모두 기존의 엔티티이다.
기본형이 아니면 IllegalArgumentException 발생
getIdType()
메소드는 어떻게 타입을 반환하는걸까? 궁금해서 추가로 조사한 결과.
EntityInformation<T, TD>
인터페이스의 Class<ID> getIdType()
을
JpaMetamodelEntityInformation
에서 구현중인걸 볼 수 있었다.
public class JpaMetamodelEntityInformation<T, ID> extends JpaEntityInformationSupport<T, ID> {
private final IdMetadata<T> idMetadata;
...
// 185 line
public Class<ID> getIdType() {
// 타고 타고 들어가면,
return type.getJavaType(); <= 요 메소드 호출로 끝납니다.
}
MetaModel
이라는 객체가 어노테이션 기반으로 필드를 찾는다고 한다.
(리플렉션인가?) 좀더... 찾아볼랬지만, 너무 깊어서 이해가 안되는지로 패스..
어차피 중요한 친구인거 같아 계속 공부하다보면 다시 볼꺼 같기도..
@StaticMetamodel()
어노테이션으로 컴파일타임 정적 메타모델 클래스를 생성할 수도 있다고 한다.
신규 엔티티 판정여부에서 추가로 조건이 걸리는 경우가 있는데,
해당 엔티티가 @Version 어노테이션을 가진 필드를 들고있는 경우가 있다고 한다.
(찾아보니 요건 낙관적 락을 사용할때의 핵심 어노테이션이라고 한다 👀)
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Version <- 이런 경우
private Long version;
}
@Version 의 유무를 판별하고, 이를 이용해 isNew()
를 override 하는 로직은 어디 구현되어 있을까? 를 찾아보니
이 또한 JpaMetamodelEntityInformation
에서 구현중이라는걸 알 수 있었다.
상당히 중요한 로직이 모여있는 클래스인것 같은데..
public class JpaMetamodelEntityInformation<T, ID> extends JpaEntityInformationSupport<T, ID> {
...
private final Optional<SingularAttribute<? super T, ?>> versionAttribute;
...
// 220 line
@Override
public boolean isNew(T entity) {
if (versionAttribute.isEmpty() <= 이부분에서 version이 지정되어있는지 체크!
|| 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);
}
혹은 엔티티의 신규 판정 여부를 Persistable 인터페이스를 구현해 isnew()
를 직접 설정할 수 있다.
public interface Persistable<ID> {
@Nullable
ID getId();
boolean isNew();
}
음.. 다 정리하고 나니 답변 링크가 제공된다는걸 뒤늦게 알았다.
그래도 혼자 공부해보고, 답변과 얼마나 차이나는지 비교해보는것도 나름 괜찮을듯..?