• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

[스프링 데이터 JPA 페이징과 정렬] left join 쿼리

23.05.28 21:13 작성 23.05.29 12:03 수정 조회수 1.24k

0

MemberRepository

public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findMemberByUsernameAndAgeGreaterThan(String username, int age);

    ...

    @Query(value = "select m from Member m left join m.team t",
            countQuery = "select count(m.username) from Member m")
    Page<Member> findByAge(int age, Pageable pageable);
}

 

MemberRepositoryTest

package hello.datajpa.repository;

...

@SpringBootTest
@Transactional
@Rollback(false)
class MemberRepositoryTest {

    @Autowired MemberRepository memberRepository;
    @Autowired TeamRepository teamRepository;

    ...

    @Test
    public void paging() {
        for (int i = 1; i <= 5; i++) {
            Member member = new Member("member" + i, 10);
            memberRepository.save(member);
        }

        int age = 10;
        PageRequest pageRequest = PageRequest.of(0, 3,
                Sort.by(Sort.Direction.DESC, "username"));
        Page<Member> page = memberRepository.findByAge(age, pageRequest);

        List<Member> content = page.getContent();
        long totalElements = page.getTotalElements();
        int totalPages = page.getTotalPages();
        int currentPage = page.getNumber();
        boolean isFirst = page.isFirst();
        boolean hasNext = page.hasNext();

        assertThat(content.size()).isEqualTo(3);
        assertThat(totalElements).isEqualTo(5);
        assertThat(totalPages).isEqualTo(2);
        assertThat(currentPage).isEqualTo(0);
        assertThat(isFirst).isTrue();
        assertThat(hasNext).isTrue();
    }
}

 

Member

package hello.datajpa.entity;

...

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "username", "age"})
/*@NamedQuery(
        name = "Member.findByUsername",
        query = "select m from Member m where m.username = :username"
)*/
public class Member {
    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "team_id")
    private Team team;

    public Member(String username, int age) {
        this.username = username;
        this.age = age;
    }

    public Member(String username, int age, Team team) {
        this.username = username;
        this.age = age;
        changeTeam(team);
    }

    public void changeTeam(Team team) {
        if (team == null) throw new RuntimeException("Member.changeTeam: team is null");
        this.team = team;
        team.getMembers().add(this);
    }
}

 

쿼리

select
        m1_0.member_id,
        m1_0.age,
        m1_0.team_id,
        m1_0.username 
    from
        member m1_0 
    order by
        m1_0.username desc offset ? rows fetch first ? rows only




select
        count(m1_0.username) 
    from
        member m1_0

강의와 다르게 left join을 해도 left join 관련 쿼리가 나가지 않는데 뭔가 강의와 다르게 설정된 부분이 있는 걸까요?


@SpringBootTest
@Transactional
@Rollback(false)
class MemberRepositoryTest {

    @Autowired MemberRepository memberRepository;
    @Autowired TeamRepository teamRepository;
    
    ...

    @Test
    public void paging() {
        Team team = new Team("A");
        teamRepository.save(team);

        for (int i = 1; i <= 5; i++) {
            Member member = new Member("member" + i, 10);
            member.changeTeam(team);
            memberRepository.save(member);
        }

        int age = 10;
        PageRequest pageRequest = PageRequest.of(0, 3,
                Sort.by(Sort.Direction.DESC, "username"));
        Page<Member> page = memberRepository.findByAge(age, pageRequest);

        List<Member> content = page.getContent();

        assertThat(team).isSameAs(content.get(0).getTeam()); // team == content.get(0).getTeam() ?

        long totalElements = page.getTotalElements();
        int totalPages = page.getTotalPages();
        int currentPage = page.getNumber();
        boolean isFirst = page.isFirst();
        boolean hasNext = page.hasNext();

        assertThat(content.size()).isEqualTo(3);
        assertThat(totalElements).isEqualTo(5);
        assertThat(totalPages).isEqualTo(2);
        assertThat(currentPage).isEqualTo(0);
        assertThat(isFirst).isTrue();
        assertThat(hasNext).isTrue();
    }
}

테스트 코드를 위와 같이 변경했을 때 left join 쿼리나 select 쿼리가 나가지 않고 assertThat(team).isSameAs(content.get(0).getTeam()) 테스트는 통과됩니다.

위 코드는 이런 식으로 동작하는게 맞을까요?

  1. fetch join이 아니기 때문에 하이버네이트는 일단 member만 가져오는 쿼리를 날리고 레코드를 받아온다.

  2. 받아온 레코드와 영속성 컨텍스트를 비교한다.

  3. 받아온 레코드가 영속성 컨텍스트에 엔티티로 존재하므로 영속성 컨텍스트에 있는 엔티티를 반환한다.

  4. 따라서 조회한 content.get(0) 객체에는 team 필드가 이미 할당 되어 있다.

답변 3

·

답변을 작성해보세요.

2

안녕하세요. 블티님

코드만 보면 left join이 나가야 할 것 같은데요. 문제 해결을 위해서는 전체 코드를 봐야 할 것 같습니다.

실제 동작하는 전체 프로젝트를 압축해서 구글 드라이브로 공유해서 링크를 남겨주세요.

구글 드라이브 업로드 방법은 다음을 참고해주세요.

https://bit.ly/3fX6ygx

 

주의: 업로드시 링크에 있는 권한 문제 꼭 확인해주세요

 

추가로 다음 내용도 코멘트 부탁드립니다.

1. 문제 영역을 실행할 수 있는 방법

2. 문제가 어떻게 나타나는지에 대한 상세한 설명

감사합니다.

블티님의 프로필

블티

질문자

2023.05.31

1. test/java/hello.datajpa.repository.MemberRepositoryTest에서 paging 메서드를 실행했을 때 left join 쿼리와 Team select 쿼리가 나가지 않습니다.

2. paging 메서드를 실행했을 때 left join 쿼리와 Team select 쿼리가 나가지 않습니다. 대신 데이터를 삽입하고 난 뒤 em.flush(); em.clear();를 해주면 left join 쿼리는 나가지 않고 Team select 쿼리는 나갑니다.

블티님 권한이 닫혀있습니다. 다음을 확인해서 권한을 풀어주세요.

https://bit.ly/3fX6ygx

주의: 업로드시 링크에 있는 권한 문제 꼭 확인해주세요

블티님의 프로필

블티

질문자

2023.06.02

https://drive.google.com/file/d/1CPv0QH1_lY_QrUDVBjc1pCQHM1ROUbdV/view?usp=sharing

권한을 바꿔놓고 확인 버튼을 안눌렀었나보네요... 죄송합니다 권한 풀었습니다

1

안녕하세요. 블티님

우선 SQL에 대한 기본 이해가 있다고 가정하겠습니다.

혹시 그렇지 않다면 SQL의 left join을 먼저 학습하셔야 합니다.

작성하신 JPQL을 보면 left join을 사용하고 있습니다.

select m from Member m left join m.team t

Member와 Team을 조인을 하지만 사실 이 쿼리를 Team을 전혀 사용하지 않습니다. select 절이나, where절에서 사용하지 않는 다는 뜻입니다.

그렇다면 이 JPQL은 사실상 다음과 같습니다.

select m from Member m

left join이기 때문에 왼쪽에 있는 member 자체를 다 조회한다는 뜻이 됩니다.

만약 select나, where에 team의 조건이 들어간다면 정상적인 join문이 보입니다.

JPA는 이 경우 최적화를 해서 해당 join없이 해당 내용만으로 SQL을 만듭니다.

 

여기서 만약 Member와 Team을 하나의 SQL로 한번에 조회하고 싶으시다면 JPA가 제공하는 fetch join을 사용해야 합니다. (fetch join은 JPA 기본편 참고)

select m from Member m left join fetch m.team t

이 경우에도 SQL에서 join문은 정상 수행됩니다.

감사합니다.

블티님의 프로필

블티

질문자

2023.06.02

그러면 섹션 4 <스프링 데이터 JPA 페이징과 정렬> 강의 21분 47초에서 22분 12초 사이에서 left join 쿼리가 나간건 강의 촬영 당시에는 JPA가 이 부분을 최적화 해주지 않았기 때문이라고 이해하면 될까요?

블티님 맞는 것 같습니다.

촬영버전은 hibernate5를 사용하고, 블티님이 사용하신 버전은 hibernate6입니다.

hibernate6으로 업그레이드 되면서 내부 파서에 큰 변화가 있었습니다.

이 부분은 메뉴얼에 업데이트를 해야겠네요. 고맙습니다^^!

0

인프런 AI 인턴님의 프로필

인프런 AI 인턴

2023.05.30

안녕하세요, 인프런 AI 인턴이에요.

left join을 해도 left join 관련 쿼리가 나가지 않는 것은 fetch join이 아니기 때문입니다. 하이버네이트는 일단 member만 가져오는 쿼리를 날리고 레코드를 받아온 뒤, 받아온 레코드와 영속성 컨텍스트를 비교합니다. 받아온 레코드가 영속성 컨텍스트에 엔티티로 존재하므로 영속성 컨텍스트에 있는 엔티티를 반환하게 됩니다. 따라서 조회한 content.get(0) 객체에는 team 필드가 이미 할당되어 있는 것입니다. 테스트 코드는 이런 식으로 동작합니다. 감사합니다.