Spring Boot - Spring Data Jpa

묘한묘랑·2023년 8월 17일
0

ShoppingMall

목록 보기
5/7

개발 도중 JpaRepository를 사용 할 때 언제 persist가 이루어 지는지 궁금하기 시작했다.

궁금하면 어떻게? 뜯어보자.

다행히 Spring Data Jpa에서는 SimpleJpaRepository를 구현체로 가지게 된다는 사실을 알고 있었기에 바로 찾을 수 있었다.
만약 몰랐다면 Break Point를 걸어 디버깅을 통하여 어떤 코드를 실행하게 되는지 파고드는 형식으로 진행하게 됐을 것이다.

	@Transactional
	@Override
	public <S extends T> S save(S entity) {

		Assert.notNull(entity, "Entity must not be null");

		if (entityInformation.isNew(entity)) {
			em.persist(entity);
			return entity;
		} else {
			return em.merge(entity);
		}
	}

Simple Data Jpa를 뜯어보니 Save 단계에서 영속성을 가지게 된다.
코드를 분석해보니 null값이 들어오게 된다면 IllegalArgumentException을 던지게 되며 현재 entity가 새로운 것인지 isNew를 통하여 확인한다.
그럼 isNew 코드에서 Context에 올라오게 되는지 궁금하여 코드를 확인해보려 하니 구현체를 5개를 가지고 있기에 디버깅이 필요하게 되었다.

디버깅을 통하여 알게 된 것은 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);
	}

새로운 Data를 넣기 때문에 if문은 true로 들어가기 때문에 super.isNew가 실행 된다.

	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의 Type이 Primitive일 경우 Null, Number일 경우 0L로 비교하여 true Or flase를 확인 한다.

만약 Id값이 있는 상태로 가면 어떻게 될까 궁금했기에 디버깅 모드로 id에 값을 주고 실행 시켜 보았다.

Id 값이 있으므로 false가 반환 되어 merge가 실행되게 된다.

2023-08-17T11:38:43.971+09:00  INFO 11936 --- [           main] p6spy                                    : [statement] | 6 ms | 
    select
        m1_0.id,
        i1_0.id,
        i1_0.name,
        i1_0.phone_number,
        a1_0.member_info_id,
        a1_0.id,
        a1_0.city,
        a1_0.detail,
        m1_0.member_role,
        m1_0.nick_name,
        m1_0.pw,
        m1_0.refresh_token,
        m1_0.user_id       
    from
        member m1_0       
    left join
        member_info i1_0               
            on m1_0.id=i1_0.member       
    left join
        member_address a1_0               
            on i1_0.id=a1_0.member_info_id       
    where
        m1_0.id=10

select문이 발생 되며, 연관관계에 있는 것들은 전부 join시켜 가져오기 때문에 성능에 문제가 있을 것 같다.
generateValue, GenerateType.IDENTITY를 사용하는 이유는 db에게 기본키를 생성 시키도록 하여 select문을 굳이 발생시키지 않도록 동작한다.
또한 persist가 되자 마자 곧바로 insert Query문이 발생 되었으며 그로 인해 id값을 바로 부여 받고, 객체에도 Id값이 들어가게 된다.

그렇다면 Dirty Checking 기술은 Context에 저장 되어 있는 Entity와 비교하여 작동할 것이라 생각된다.
그렇다면 언제 그것을 정하게 되는지 궁금해 찾아보니 transaction이 끝나는 지점에 실행 된다는 사실을 알게 되었다.

Reference Blog

profile
상황에 맞는 기술을 떠올리고 사용할 수 있는 개발자가 되고 싶은 개발자

0개의 댓글