해결된 질문
작성
·
72
·
수정됨
0
이전 강의를 수강 하면서 엔티티 조회 시 지연 로딩 설정 시 즉시 조회가 되는 게 아닌 프록시 객체가 데이터의 위치를 가지고 사용할 때에 프록시 객체가 초기화 되며 데이터베이스에 쿼리를 요청하고 데이터를 받아 온다 라고 배웠습니다. 그렇다면 테스트 메서드를 실행하게 된다면
Team, Member 객체를 persist -> 영속성 컨텍스트에 Member와 Team 존재
flush() -> 영속성 컨텍스트에 들어있던 엔티티를 데이터베이스에 반영 -> insert 쿼리가 실행
데이터베이스에 반영되었으나 아직 영속성 컨텍스트에는 값이 존재하는 상태
clear() -> 영속성 컨텍스트에 들어 있는 데이터를 초기화
createQuery로 Member 엔티티를 데이터베이스에서 조회 -> Member에 대한 select 쿼리 실행
영속성 컨텍스트가 초기화 되었고 team이 지연 로딩으로 설정되어 프록시 객체가 생성
루프를 돌며 getTeam() 호출 시점 team_id를 기반으로 데이터베이스에 team을 조회 -> Team에 대한 select 쿼리 실행
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를 사용할 때 지연 로딩과 프록시 초기화에 대한 문제를 다루고 계시군요. 질문 내용에서 테스트 코드와 해당 실행 결과를 상세히 설명해주셔서 감사합니다. 이 문제와 유사한 질문과 답변이 기존에 게시된 적이 있으며, 해당 내용이 도움이 될 수 있을 것 같습니다.
MemberTest
와 MemberRepositoryTest
의 프록시 초기화 관련 문제를 다루고 있습니다. 프록시 초기화가 되었는지 확인하는 과정과 println()
문이 호출될 때의 프록시 초기화 동작에 대해 설명하고 있습니다.위 질문들에서 공통적으로 다룬 주제는 “지연 로딩(LAZY Loading)과 프록시 객체의 초기화”입니다. JPA에서 프록시 객체는 데이터베이스와의 불필요한 통신을 줄이기 위해 필요한 순간까지 실제 데이터베이스 접근을 지연시키는 중요한 역할을 합니다. 그러나 프록시는 잘못 이해하면 예상치 못한 동작을 초래할 수 있습니다.
질문하신 코드에서 member.getTeam()
을 호출할 때 특정 조건에서 프록시가 초기화되지 않는 이유는 여러 원인이 있을 수 있습니다. 다음과 같은 점들을 고려해보세요:
프록시 초기화: member.getTeam()
호출 시 프록시 객체가 초기화될 것으로 예상했으나, 실제 데이터베이스 쿼리가 나가지 않는 경우는 두 가지 가능성이 있습니다.
프록시 메서드 호출: 프록시의 메서드를 호출하는 순간 초기화됩니다. 예를 들어 member.getTeam().getName()
와 같은 메서드 호출이 프록시를 초기화합니다.
프록시 초기화 확인: log.info("team.isInitialized = {}", Hibernate.isInitialized(member.getTeam()));
구문을 통해 초기화 여부를 확인할 수 있습니다. 코드에서 이 정보를 확인하는 부분도 매우 잘 구현되었습니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.