테스트 코드에서 리포지토리의 Entity Manager에 직접 접근 시 null이 뜨는 이유

eora21·2022년 8월 27일
0

문제

    @Test
    public void bulkUpdate() throws Exception {
        //given
        memberJpaRepository.save(new Member("member1", 10));
        memberJpaRepository.save(new Member("member2", 19));
        memberJpaRepository.save(new Member("member3", 20));
        memberJpaRepository.save(new Member("member4", 21));
        memberJpaRepository.save(new Member("member5", 40));

        //when
        int resultCount = memberJpaRepository.bulkAgePlus(20);

        //then
        assertThat(resultCount).isEqualTo(3);

        memberJpaRepository.em.flush();
        memberJpaRepository.em.clear();

        List<Member> all = memberJpaRepository.findAll();
        for (Member member : all) {
            System.out.println("member = " + member);
        }
    }

테스트 코드에서 벌크 업데이트가 제대로 먹히는지 확인하기 위해, 리포지토리 em에 직접 접근하여 flush, clear를 해주기로 했다. 좋은 코드는 아니지만 어차피 테스트 코드이기에 시도해봤다.

근데 NPE가 발생했다.

해결

리포지토리에 해당 함수를 작성하고

//        memberJpaRepository.em.flush();
//        memberJpaRepository.em.clear();
        
        memberJpaRepository.flush();
        memberJpaRepository.clear();

방금 만든 함수를 돌려 flush와 clear 동작을 시켰다.
원하는 대로 sout이 잘 나오는 것을 확인을 했다.. 만 궁금증이 풀리지 않는다. 왜 테스트 코드에서 리포지토리의 em은 null이 나올까?

이유 탐색

리포지토리와 테스트코드에서 출력하며 확인

리포지토리에 해당 코드를 작성했다.

	@Test
    public void getEntityManager() throws Exception {
        //given
        EntityManager em = memberJpaRepository.getEm();

        //when
        memberJpaRepository.emStatus();

        //then
        System.out.println("em = " + em);
        System.out.println("em = " + memberJpaRepository.em);
    }

리포지토리에서 얻어온 em, 리포지토리에서 확인한 em, 테스트코드에서 살펴본 em을 다 출력해보도록 했다.

리포지토리에서 얻어오거나 확인한 객체는 proxy로 잡히는데, 테스트코드에서 em 직접접근은 null이 출력됐다. 왜지..?

@Repository -> @Component

리포지토리 어노테이션을 컴포넌트로 변경하고 getEntityManager 테스트코드를 다시 돌려보았다.

이번엔 다 프록시 객체로 뜬다..? 무슨 차이였을까?

@Component

일반 컴포넌트 빈으로 잡힌 걸 알 수 있다.

@Repository

방금 전과 달리 CGLIB 빈이 잡혀있다.

CGLIB란?

코드 생성 라이브러리로서, 런타임 시 동적으로 자바 클래스의 proxy를 생성해주는 기능이라고 한다.
즉, @Repository 사용 시 proxy 빈으로 생성되고 있었다..!

@Repository는 상황에 따라 proxy로 래핑된 Bean 혹은 target class 타입의 Bean이 생성된다고 한다.
따라서 필드는 주입받아 사용할 수 없는 상태..!

외전) flush와 지연 SQL 저장소

해당 테스트코드를 계속 터쳐가며 확인하고 있었는데, persist를 한 멤버 객체들이 테스트 코드가 중단되기 전까진 DB에 없다가 NPE가 뜨는 순간 생성되는 걸 확인했다. 왜일까?

해당 테스트 코드 기준으로 보자면, 멤버 영속화는 DB에 저장되는 게 아닌 1차 캐시(영속성 컨텍스트)에 저장되며 해당 쿼리는 쓰기 지연 SQL 저장소에 작성된다.

벌크 쿼리도 마찬가지로 쓰기 지연 SQL 저장소에 작성된다.
(이 때 FlushModeType이 AUTO라면 flush도 같이 수행된다.)

flush는 영속성 컨텍스트 내의 수정된 엔티티가 있다면 쓰기 지연 SQL 저장소에 UPDATE SQL을 등록하고, 쓰기 지연 SQL 저장소에 있는 SQL들을 DB에 반영한다(동기화라 보면 된다).

이 때의 영속성 컨텍스트 내부 데이터는 벌크 쿼리가 수행되기 이전 상태의 멤버 데이터들을 들고 있으며, 수정된 데이터는 없다.

따라서 UPDATE SQL은 작성되지 않으며, 지금까지 모인 멤버 영속화 쿼리와 벌크 쿼리가 DB에 반영된다.

DB에는 해당 쿼리들이 작성된 상태지만, 아직 commit 요청이 들어오지 않았기 때문에 DB 내부 데이터에는 반영되지 않는다. (어디에 저장되는걸까? 이것도 조사 후 따로 게시해야겠다.)

이 때 NPE가 터진다면, commit 없이 롤백되는 게 맞지만 @Rollback(value = false)를 걸어둔 상태라 강제 commit이 찍히는 것 같다.

profile
나누며 타오르는 프로그래머, 타프입니다.

0개의 댓글