<인프런의 김영한님의 강의를 보고 정리한 내용입니다>
회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에)
SQL을 보면 회원 뿐만 아니라 팀(T.*)도 함께 SELECT
/* 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
한 예시를 보면
이 구조에서 fetch join을 통해서 [MEMBER JOIN TEAM] 을 만드려고 한다.
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
private int age;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
// .. 이하 생략
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
// ..이하 생략
}
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query = "select m from Member m";
List<Member> result = em.createQuery(query, Member.class).getResultList();
for (Member member : result) {
System.out.println("member = " + member.getUsername() + ", " + member.getTeam().getName());
}
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
일단 이렇게 출력을 한다고 하자, 쿼리를 확인해보면
이렇게 member 중 팀들의 수 만큼 쿼리문이 나가는 것을 알 수 있다.
좀 더 자세히 보면, 회원1은 팀A를 SQL 쿼리로 가져오게 되고 영속성 컨텍스트에 들어가게 된다. 그리고 회원2는 팀A가 영속성 컨텍스트에 있기 때문에 영속성 컨텍스트에서 가져온다. 마지막으로 회원3은 팀B를 SQL 쿼리로 가져온다.
근데 여기서 만약 100명의 멤버 모두가 팀이 다르다면 100개의 쿼리가 나가게 될 것이다. 즉 N+1 문제가 발생한다. 이 문제를 해결하기 위해서 fetch join을 사용한다.
String query = "select m from Member m join fetch m.team";
query를 이렇게 바꿔주면 되는데 결과를 보면
이렇게 한 번의 쿼리문이 나갔다.
System.out.println("member = " + member.getUsername() + ", " + member.getTeam().getName());
여기서 getTeam()이 중요한데 여기서는 프록시가 아니라 진짜 데이터이다. 그래서 조회할 때 쿼리로 조회하지 않는다.
일대다 관계, 컬렉션 페치 조인
/* 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 WGHERE T.NAME='팀A'
예시를 보면,
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query = "select t From Team t join fetch t.members";
List<Team> result = em.createQuery(query, Team.class).getResultList();
for (Team team : result) {
System.out.println("team = " + team.getName() + "|members=" + team.getMembers().size());
}
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
emf.close();
}
}
이렇게 진행된다고 할 때, 결과를 보면
뭔가 잘 된 것 같지만 똑같은 팀A가 두번 나온 것을 알 수 있다. 왜 그럴까?
조인을 하면서 멤버 수 만큼 팀A에 대해서 멤버가 2명이기에 2줄이 만들어지기 때문이다.
이 때, 사용하는 것이 DISTINCT이다.
SQL의 DISTINCT는 중복된 결과를 제거하는 명령인데,
JPQL의 DISTINCT는 2가지 기능을 제공한다.
String query = "select distinct t From Team t join fetch t.members";
쿼리를 이렇게 입력하면 애플리케이션에서 엔티티 중복도 제거해주기 때문에,
제대로 된 결과를 얻을 수 있다.
일반 조인 실행 시, 연관된 엔티티를 함께 조회하지 않음.
/* JPQL */
select t 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'
단순히 join만을 하여
String query = "select t From Team t join t.members";
결과를 확인해보면
팀에 대한 것들만 조회를 한다. 즉, member에 대한것은 조회 안한다. 그리고
멤버에 대한 조회를 진행한다.
즉 그냥 join을 하면 members에 대한 정보는 가져오지 않는다.
[ JPA ] Named Query (0) | 2022.02.03 |
---|---|
[ JPA ] 페치 조인 (fetch join) - 한계 (0) | 2022.02.02 |
[ JPA ] JPQL 경로 표현식 (0) | 2022.01.31 |
[ JPA ] jpql 페이징 (0) | 2022.01.29 |
[ JPA ] 값 타입 리스트 동작 방식 (0) | 2022.01.28 |