• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    미해결

em.find()와 영속성 컨텍스트 관련 질문

22.10.14 02:16 작성 조회수 762

1

안녕하세요!

em.find()와 영속성 컨텍스트 관련해서 학습테스트를 작성하던 도중 의문이 생겨 질문을 남깁니다.

em.find() 작업은 1차 캐시에 데이터가 존재한다면 영속성컨텍스트의 데이터를 반환하고, 1차 캐시에 데이터가 존재하지 않을 경우 DB에 select 쿼리문을 보내는 것으로 알고 있습니다.

 

위 선행지식을 바탕으로, 아래의 학습테스트 경우처럼 1차 캐시가 비어있고 DB에 데이터가 있는 경우에서 delete 메서드를 호출하고 EntityManager를 flush()하지 않은 상태로 테스트하여 delete 쿼리문을 DB에 보내지 않고 쓰기지연 저장소에 남겨둔 상태를 테스트해보도록 했습니다.

(여기서 delete는 Spring Data JPA의 delete문입니다! 이 delete 메서드에선 해당 JpaRepository의 구현체를 타겟 프록시로 하는 SimpleJpaRepository에서 em.find() 작업 후 em.remove() 작업을 해주는 것으로 알고 있습니다.)

학습 테스트


    @Test
    @DisplayName("1차 캐시가 비어있고 쓰기지연 저장소에 delete 쿼리가 있는 상태에서 em.find()를 할 경우 결과를 확인한다")
    void test7() {
        // 영속성 컨텍스트, DB에 모두 member 저장 (IDENTITY 전략)
        final Member member = new Member("kth990303", "kth990303@naepyeon.com", Platform.KAKAO, "1");
        final Long memberId = memberRepository.save(member)
                .getId();

        // 영속성 컨텍스트는 비워줌
        em.flush();
        em.clear();

        System.out.println("===========================");
        // delete 쿼리는 쓰기지연저장소에 존재하고 아직 sql로 찌르지는 않음
        memberRepository.delete(member);

        System.out.println("=============================================");
        // 1차캐시에 Member는 존재하지 않으므로 select 쿼리가 이 때 나갈 줄 알았으나 안나감. 
        em.find(Member.class, memberId);
        em.flush();
        em.clear();
    }

저는 위 테스트에 대한 결과 예측을 아래와 같이 했습니다.

  1. insert문을 DB에 날린다

  2. 이후 em.clear()로 영속성 컨텍스트를 비워주어 1차 캐시에는 데이터 존재 X

  3. ======

  4. delete 메서드 호출한다. 1차 캐시에 데이터가 존재하지 않아 select 쿼리문 후 delete 쿼리문을 날려야 함. (SimpleJpaRepository) 이 쿼리문들은 쓰기지연 저장소에 저장돼서 아직 날라가지 않음.

  5. =======

  6. em.find()를 호출하고, 1차 캐시에 데이터가 존재하지 않아 select 쿼리문이 쓰기지연저장소에 쌓임.

  7. em.flush()를 해주어 쓰기지연저장소에 있던 select, delete, select가 나갈 것이라 예측.

결과적으로 insert -> ==== -> ===== -> select, delete, select 가 나갈 것이라 생각했습니다.

하지만 실제 쿼리는 아래와 같았습니다.

실제로는 위와 같이 insert -> ==== -> select -> ===== -> delete 만 나가게 됐습니다.

이에 대한 이유가 궁금합니다!

감사합니다.

 

 

참고1. SimpleJpaRepository의 delete 메서드

@Override
@Transactional
@SuppressWarnings("unchecked")
public void delete(T entity) {

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

	if (entityInformation.isNew(entity)) {
		return;
	}

	Class<?> type = ProxyUtils.getUserClass(entity);

	T existing = (T) em.find(type, entityInformation.getId(entity));

	// if the entity to be deleted doesn't exist, delete is a NOOP
	if (existing == null) {
		return;
	}

	em.remove(em.contains(entity) ? entity : em.merge(entity));
}

참고2. 해당 질문에 대한 상황을 정리한 노션

https://clean-nutria-44b.notion.site/JPA-em-find-1-34fa1ba3df914e24ba9dd9a143f28c8c

답변 2

·

답변을 작성해보세요.

0

답변 감사합니다.

하지만 아직 제가 부족한 탓에 잘 이해가 되지 않습니다 ㅜㅜ

remove의 경우도 flush() 호출 전까지는 영속성 컨텍스트에 존재하기 때문에, delete() 코드 다음 flush()를 해주지 않으면 =====, ======== 사이에서는 아무 쿼리문도 보내지 않을 줄 알았는데, 실제로는 =====, ======== 사이에 select 쿼리문을 보내는 이유가 궁금합니다!

 

영한님 답변대로 delete() 다음 em.flush()를 하면 insert -> select, delete -> select로 의도한대로 잘 나가더라구요! 근데 delete() 이후 즉시 flush()를 해주지 않으면(즉, 위의 질문한 코드면) 아래와 같은 의문점이 드는 부분이 존재합니다.

 

  1. =====, ======== 사이에서 왜 select 쿼리가 나가는가? 이 사이에선 아무것도 쿼리가 나가지 않아야 하는 게 맞지 않을까?

  2. 왜 맨 마지막 em.find()의 select 쿼리는 나가지 않는걸까?

 

추가로 답변해주신다면 정말 감사하겠습니다.

좋은 하루 되세요 :)

0

안녕하세요. TaeHyeon Kim님

remove의 경우도 flush()를 호출하기 전까지는 영속성 컨텍스트에 존재하게 됩니다.

delete() 코드 다음에 em.flush()를 넣어보시면 이해가 되실꺼에요.

감사합니다.