[매일메일] Spring Data JPA에서 새로운 Entity인지 판단하는 방법은 무엇일까요?

신원규·2025년 3월 24일
0

매일메일

목록 보기
1/2
post-thumbnail

매일메일

매일메일 이라는 서비스가,
매일 오전 09시 기술면접 질문을 보내준다길래 구독을 신청해봤다.

질문의 답이 무엇인지 공부한 기록을 시간 되는대로 기록해봐야지.
지금은 BE/FE 두 직군에 대해서만 질문을 제공하는듯?

오늘의 질문

Spring Data JPA에서 새로운 Entity인지 판단하는 방법은 무엇일까요?

일단 하나도 몰라서 차근차근히 접근해봤다.

JpaRepository 의 구현부터 시작

이를 보기위해선 JpaRepository 의 기본 구현인 SimpleJpaRepositorysave()를 봐야하는데,

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() 은 어떻게 타입을 반환하지?

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 어노테이션

신규 엔티티 판정여부에서 추가로 조건이 걸리는 경우가 있는데,
해당 엔티티가 @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();
}

3줄요약

  1. 신규 여부 판정은, 기본형 ID 가 아니면 직접 구현해야한다.
  2. 기본형은 판정 로직을 제공해주는데, 숫자면 0, 다른 타입의 경우 null 이면 신규로 판정한다.
  3. @Version 어노테이션이 붙어있으면 이것도 확인한다.

음.. 다 정리하고 나니 답변 링크가 제공된다는걸 뒤늦게 알았다.

그래도 혼자 공부해보고, 답변과 얼마나 차이나는지 비교해보는것도 나름 괜찮을듯..?

profile
생존형 개발자. 어디에 던져져도 살아 남는것이 목표입니다.

0개의 댓글