해결된 질문
작성
·
2.2K
·
수정됨
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())
테스트는 통과됩니다.
위 코드는 이런 식으로 동작하는게 맞을까요?
fetch join이 아니기 때문에 하이버네이트는 일단 member만 가져오는 쿼리를 날리고 레코드를 받아온다.
받아온 레코드와 영속성 컨텍스트를 비교한다.
받아온 레코드가 영속성 컨텍스트에 엔티티로 존재하므로 영속성 컨텍스트에 있는 엔티티를 반환한다.
따라서 조회한 content.get(0)
객체에는 team 필드가 이미 할당 되어 있다.
답변 3
2
안녕하세요. 블티님
코드만 보면 left join이 나가야 할 것 같은데요. 문제 해결을 위해서는 전체 코드를 봐야 할 것 같습니다.
실제 동작하는 전체 프로젝트를 압축해서 구글 드라이브로 공유해서 링크를 남겨주세요.
구글 드라이브 업로드 방법은 다음을 참고해주세요.
주의: 업로드시 링크에 있는 권한 문제 꼭 확인해주세요
추가로 다음 내용도 코멘트 부탁드립니다.
1. 문제 영역을 실행할 수 있는 방법
2. 문제가 어떻게 나타나는지에 대한 상세한 설명
감사합니다.
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://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문은 정상 수행됩니다.
감사합니다.
0
안녕하세요, 인프런 AI 인턴이에요.
left join을 해도 left join 관련 쿼리가 나가지 않는 것은 fetch join이 아니기 때문입니다. 하이버네이트는 일단 member만 가져오는 쿼리를 날리고 레코드를 받아온 뒤, 받아온 레코드와 영속성 컨텍스트를 비교합니다. 받아온 레코드가 영속성 컨텍스트에 엔티티로 존재하므로 영속성 컨텍스트에 있는 엔티티를 반환하게 됩니다. 따라서 조회한 content.get(0) 객체에는 team 필드가 이미 할당되어 있는 것입니다. 테스트 코드는 이런 식으로 동작합니다. 감사합니다.
구글 드라이브 링크입니다.
https://drive.google.com/file/d/1CPv0QH1_lY_QrUDVBjc1pCQHM1ROUbdV/view?usp=sharing