인프런 커뮤니티 질문&답변

kwang2134님의 프로필 이미지

작성한 질문수

실전! 스프링 데이터 JPA

섹션 3 지연 로딩 설정 후 MemberTest 실행 후 쿼리 문

해결된 질문

작성

·

59

·

수정됨

0

이전 강의를 수강 하면서 엔티티 조회 시 지연 로딩 설정 시 즉시 조회가 되는 게 아닌 프록시 객체가 데이터의 위치를 가지고 사용할 때에 프록시 객체가 초기화 되며 데이터베이스에 쿼리를 요청하고 데이터를 받아 온다 라고 배웠습니다. 그렇다면 테스트 메서드를 실행하게 된다면

  1. Team, Member 객체를 persist -> 영속성 컨텍스트에 Member와 Team 존재

  2. flush() -> 영속성 컨텍스트에 들어있던 엔티티를 데이터베이스에 반영 -> insert 쿼리가 실행

  3. 데이터베이스에 반영되었으나 아직 영속성 컨텍스트에는 값이 존재하는 상태

  4. clear() -> 영속성 컨텍스트에 들어 있는 데이터를 초기화

  5. createQuery로 Member 엔티티를 데이터베이스에서 조회 -> Member에 대한 select 쿼리 실행

  6. 영속성 컨텍스트가 초기화 되었고 team이 지연 로딩으로 설정되어 프록시 객체가 생성

  7. 루프를 돌며 getTeam() 호출 시점 team_id를 기반으로 데이터베이스에 team을 조회 -> Team에 대한 select 쿼리 실행

  8. memberA 조회 후 teamA에 대한 조회 쿼리가 발생하고 memberC 조회 후 teamB 조회 쿼리가 발생

이러한 과정으로 이뤄진다고 배웠는데 테스트 코드를 돌려보니 member에 대한 조회 쿼리 후 team에 대한 조회 쿼리가 발생하지 않습니다.

그래서 팀의 프록시 객체가 초기화 되었는지 체크하기 위해 Hibernate.isInitialized를 사용해 찍어봤는데 제일 처음 false 로 초기화 되지 않았다고 나오나 team을 조회하는 쿼리가 발생하지 않았습니다.

찾다 보니 Hibernate에 쿼리 최적화 기능으로 영속성 컨텍스트는 초기화 되었지만 메모리가 초기화 된 것은 아니므로 해당 객체가 메모리에 존재한다면 Hibernate가 쿼리를 생략하고 해당 객체를 반환한다라는 게 있던데 그것 때문에 쿼리가 나가지 않는 건지 궁금합니다. 2차 캐시가 설정되어 있지는 않습니다.

 

스프링 부트 3.3.4, 자바 17, Hiberante 6.5.3 입니다!

* 해결 *

spring.jpa.properties.hibernate.show_sql=true

하이버네이트 show_sql이 주석처리 format_sql만 출력되는 상황이었습니다.

정상 출력 확인했습니다!

package springPJ.dataJpa.domain;

import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.Hibernate;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@Transactional
@RequiredArgsConstructor
@Slf4j
class MemberTest {

    @Autowired
    EntityManager em;

    @Test
    void testEntity() {
        Team teamA = Team.builder().name("teamA").build();
        Team teamB = Team.builder().name("teamB").build();
        em.persist(teamA);
        em.persist(teamB);

        Member memberA = Member.builder().name("memberA").age(10).team(teamA).build();
        Member memberB = Member.builder().name("memberB").age(20).team(teamA).build();
        Member memberC = Member.builder().name("memberC").age(30).team(teamB).build();
        Member memberD = Member.builder().name("memberD").age(40).team(teamB).build();

        em.persist(memberA);
        em.persist(memberB);
        em.persist(memberC);
        em.persist(memberD);

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

        List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();

        for (Member member : members) {
            log.info("team.isInitialized = {}", Hibernate.isInitialized(member.getTeam()));
            log.info("member = {}", member);
            log.info("member.team = {}", member.getTeam());
        }
    }

}
    select
        m1_0.member_id,
        m1_0.age,
        m1_0.name,
        m1_0.team_id 
    from
        member m1_0
2024-09-21T19:36:05.194+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : team.isInitialized = false
2024-09-21T19:36:05.194+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : member = Member(id=1, name=memberA, age=10)
2024-09-21T19:36:05.197+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : member.team = Team(id=1, name=teamA)
2024-09-21T19:36:05.219+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : team.isInitialized = true
2024-09-21T19:36:05.219+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : member = Member(id=2, name=memberB, age=20)
2024-09-21T19:36:05.219+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : member.team = Team(id=1, name=teamA)
2024-09-21T19:36:05.219+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : team.isInitialized = false
2024-09-21T19:36:05.219+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : member = Member(id=3, name=memberC, age=30)
2024-09-21T19:36:05.219+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : member.team = Team(id=2, name=teamB)
2024-09-21T19:36:05.219+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : team.isInitialized = true
2024-09-21T19:36:05.220+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : member = Member(id=4, name=memberD, age=40)
2024-09-21T19:36:05.220+09:00  INFO 7696 --- [dataJpa] [    Test worker] springPJ.dataJpa.domain.MemberTest       : member.team = Team(id=2, name=teamB)

 

답변 1

0

안녕하세요, 인프런 AI 인턴입니다. kwang2134님.

JPA와 Hibernate를 사용할 때 지연 로딩과 프록시 초기화에 대한 문제를 다루고 계시군요. 질문 내용에서 테스트 코드와 해당 실행 결과를 상세히 설명해주셔서 감사합니다. 이 문제와 유사한 질문과 답변이 기존에 게시된 적이 있으며, 해당 내용이 도움이 될 수 있을 것 같습니다.

관련 기존 질문들:

  1. 프록시 초기화 관련 질문

    • 이 질문에서는 MemberTestMemberRepositoryTest의 프록시 초기화 관련 문제를 다루고 있습니다. 프록시 초기화가 되었는지 확인하는 과정과 println() 문이 호출될 때의 프록시 초기화 동작에 대해 설명하고 있습니다.
  2. mappedBy로 조회할 때 질문. (프록시의 관계)

    • 이 질문에서는 JPA의 프록시 객체 사용과 관련된 기본적인 개념과 동작 방식을 다룹니다. 메모리와 영속성 컨텍스트의 초기화에 대한 이해를 돕기 위해 관련 내용을 설명합니다.
  3. 영속성 컨텍스트와 LAZY 로딩 관계

    • 해당 질문에서는 LAZY 로딩 설정 시의 프록시 사용 방법 및 동작 원리에 대해 자세히 설명합니다. 특히 프록시 객체가 언제 초기화되는지에 대한 구체적인 예를 다룹니다.
  4. 일대다 지연로딩에 관한 질문입니다.

    • 일대다 관계에서 지연 로딩 시 프록시 객체가 어떻게 동작하는지에 대한 질문입니다. 실제로 프록시 객체가 어떻게 메모리에 로딩되고 초기화되는지 설명합니다.

위 질문들에서 공통적으로 다룬 주제는 “지연 로딩(LAZY Loading)과 프록시 객체의 초기화”입니다. JPA에서 프록시 객체는 데이터베이스와의 불필요한 통신을 줄이기 위해 필요한 순간까지 실제 데이터베이스 접근을 지연시키는 중요한 역할을 합니다. 그러나 프록시는 잘못 이해하면 예상치 못한 동작을 초래할 수 있습니다.

풍부한 설명:

질문하신 코드에서 member.getTeam()을 호출할 때 특정 조건에서 프록시가 초기화되지 않는 이유는 여러 원인이 있을 수 있습니다. 다음과 같은 점들을 고려해보세요:

  • 프록시 초기화: member.getTeam() 호출 시 프록시 객체가 초기화될 것으로 예상했으나, 실제 데이터베이스 쿼리가 나가지 않는 경우는 두 가지 가능성이 있습니다.

    1. 1차 캐시: 영속성 컨텍스트는 1차 캐시를 사용하여 이미 로드된 엔티티에 대해서는 새 쿼리를 하지 않습니다. 이 점을 고려하면 편리합니다.
    2. Hibernate 최적화: Hibernate가 내부적으로 특정 객체가 이전에 로드되었음을 알고 데이터를 다시 로드하지 않도록 최적화할 수 있습니다.
  • 프록시 메서드 호출: 프록시의 메서드를 호출하는 순간 초기화됩니다. 예를 들어 member.getTeam().getName()와 같은 메서드 호출이 프록시를 초기화합니다.

  • 프록시 초기화 확인: log.info("team.isInitialized = {}", Hibernate.isInitialized(member.getTeam())); 구문을 통해 초기화 여부를 확인할 수 있습니다. 코드에서 이 정보를 확인하는 부분도 매우 잘 구현되었습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.