실무에서 자주 사용되며 정말 중요하다
성능 최적화
를 위해 제공하는 기능SQL 한번에 함께 조회
하는 기능연관된 팀도 함께 조회
(SQL 한번에)//JPQL
select m from Member m join fetch m.team
//SQL
select m.* t.* from Member m inner join Team t on m.team_id = t.id;
String jpql = "select m from Member m";
List<Member> members = em.createQuery(jpql, Member.class)
.getResultList();
for (Member member : members) {
System.out.println("username = " + member.getUsername() + ", " +
"teamName = " + member.getTeam().name());
//회원1, 팀A(SQL)
//회원2, 팀A(1차 캐시)
//회원3, 팀B(SQL)
//회원 100명 -> N + 1
}
최초 jpql을 통해 Member를 조회할 때, Team의 정보를 프록시로 가져온다.
getTeam(), getName()을 통해 팀의 정보를 조회하려고 할때, 실제 객체를 SQL을 수행하여 가져온다.
한번 가져온 Team의 정보는 1차 캐시에 올라가 있으므로 다시 조회할 필요는 없지만, 회원 N명을 조회할 때 최대 N+1번의 쿼리가 수행될 수 있다.
String jpql = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(jpql, Member.class)
.getResultList();
for (Member member : members) {
//페치 조인으로 회원과 팀을 함께 조회해서 지연 로딩X
System.out.println("username = " + member.getUsername() + ", " +
"teamName = " + member.getTeam().name());
}
페치 조인은 조회 당시에 실제 엔티티가 담기므로 지연로딩 없이 바로 사용이 가능하다.
//JPQL
select t from Team t join fetch t.members where t.name = '팀A';
//SQL
select t.*, m.* from team t, inner join member m on t.id = m.team_id
where t.name = '팀A';
➡️Team은 하나지만, Member가 1개 이상일 수도 있다.
- 팀A는 1개이지만 그에 해당하는 멤버는 회원1과 회원2로 두명이므로 조회결과는 2개의 row가 된다.
- 팀은 하나이기에 같은 주소값을 가진 결과가 두개 나오고, 팀 A의 입장에서는 회원1, 회원2를 총 두개를 가진다.
String jpql = "select t from Team t join fetch t.members where t.name = '팀A'"
List<Team> teams = em.createQuery(jpql, Team.class).getResultList();
for (Team team : teams) {
System.out.println("teamname = " + team.getName() + ", team = " + team);
for (Member member : team.getMembers()) {
//페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println("->username = " + member.getUsername()+ ", member = " + member);
}
}
일대다 관계에서는 join fetch 결과가 뻥튀기 될 수 있다.
String query = "select t from Team t";
String query2 = "select t from Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class).getResultList();
List<Team> result2 = em.createQuery(query2, Team .class).getResultList();
System.out.println("result size::"+ result.size()); //2
System.out.println("result2 size::"+ result2.size());//3
select distinct t from Team t join fetch t.members where t.name = '팀A';
📌반대로 다대일은 뻥튀기 되지 않는다
📌하이버네이트6 변경 사항
- 하이버네이트6 부터는 DISTINCT 명령어를 사용하지 않아도 애플리케이션에서 중복 제거가 자동으로 적용 된다.
//JPQL
select from Team t join t.members m where t.name = '팀A';
//SQL
select t.* from Team t inner join member m on t.id = m.team_id
where t.name = '팀A';
실제로 해당 엔티티를 사용할 때 실제 값을 조회
한다.즉시 로딩
)객체 그래프를 SQL 한번에 조회하는 개념
이다. String query = "select t from Team t join fetch t.members, t.orders"
//불가능. fetch join에서 컬렉션은 1개만 사용하자.
String query = "select t from Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class)
.setFirstResult(0)
.setMaxResults(1)
.getResultList();