본문 바로가기

Spring/JPA

JPA- 조인

내부 조인

package jpql;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

public class JpqlMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {

            for (int i = 0; i < 5; i++) {
                // 나이가 1씩 증가하는 Member 객체 5개 생성 (0~4)
                Member member = new Member();
                member.setUsername("member"+i);
                member.setAge(i);

                // 각 Member 객체마다 team을 연관관계 매핑
                Team team = new Team();
                team.setName("team" + i);
                member.changeTeam(team);

                em.persist(member);
            }

            em.flush();
            em.clear();

            List<Member> result = em.createQuery("select m from Member m JOIN m.team t", Member.class)
                    .setFirstResult(0)
                    .setMaxResults(3)  //Member0, Member1, Member2로 이루어진 리스트 조회
                    .getResultList();

            // member == member0
            Member member = result.get(0);
            System.out.println("member.getTeam() = " + member.getTeam());  // team에 toString() 처리함.

            tx.commit();
        } catch (Exception e) {
            tx.rollback(); // 오류 발생 시 롤백
            e.printStackTrace(); // 에러 내용 출력
        } finally {
            //종료
            em.close();
        }
        emf.close();
    }
}
Hibernate: 
    /* select
        m 
    from
        Member m 
    JOIN
        m.team t */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.TEAM_ID as team_id4_0_,
            member0_.username as username3_0_ 
        from
            Member member0_ 
        inner join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id limit ?
                
member.getTeam() = Team{id=2, name='team0'}

 위의 예시에선 내부 조인 쿼리를 날리는 경우 Member 엔티티를 조회해오면서 연관되어 있는 Team 객체도 가져온다.

내부 조인 시에는 쿼리 작성시 단순히 "xxx Join xxx" 이런식으로 작성하면 편하다. (굳이 inner 안적어도 ㄱㅊ)

inner join의 경우, 두 테이블 간 일치하는 행만 반환된다.

==> Member 조회 + 연관되어 있는 Team

 

세타 조인

package jpql;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

public class JpqlMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            Team team = new Team();
            team.setName("nameTeam");

            Member member = new Member();
            member.setUsername("nameMember");
            member.setAge(22);
			
            member.changeTeam(team);
            em.persist(member);

            // 세타 조인: Member와 Team을 모두 가져온 다음 그중 where문의 조건에 맞는 것들만 추림
            String query = "select m from Member m, Team t where m.username = t.name";
            List<Member> result = em.createQuery(query, Member.class)
                    .getResultList();

            for (Member m : result) {
                System.out.println("m = " + m);
            }

            tx.commit();
        } catch (Exception e) {
            tx.rollback(); // 오류 발생 시 롤백
            e.printStackTrace(); // 에러 내용 출력
        } finally {
            //종료
            em.close();
        }
        emf.close();
    }
}

출력>>>
없다.

이 예시에서는 아예 Member와 Team을 모두 가져온 다음 그 중 서로간의 이름이 같은 녀석들만 반환한다. 지금 경우에는 Member의 이름과 Team의 이름이 다르기 때문에 아무것도 조인되지 않았다.  

Member.username과 Team.name이 같은게 없으므로 조인된 것도 없다.

이 코드에서 코드를 바꿔 두 엔티티의 이름만 같게 한다면...

// 변경 코드
team.setName("엄준식");
member.setUsername("엄준식");

출력>>>
m = Member{id=1, username='엄준식', age=22}

 

Member.username 과 Team.name이 모두 엄준식으로 같아서 조인되었음을 알 수 있다.

 LEFT JOIN

 left join을 사용하면 본 테이블은 모두 가져오고, 조인되는 테이블은 매치되는 값이 없다면 null로 가져온다. 여기서 중요한 점은 left join이라는 이름에 맞게 좌측에 있는 테이블은 모두 가져온다는 것이다.

 일반 join의 경우 조인 시 매칭되는 행이 없는 경우에 해당 행은 가져 오지 않지만 (세번째), left join의 경우에는 일단 본 테이블은 모든 행을 다 가져오고 그중 매칭되는 행은 조인하되, 매칭이 되지 않는다면 null로 표시한다. 

조인 - ON 절

ON은 조인을 할 때 필터링을 하는 기능이라고 보면된다.

String query = "select m from Member m left join Team t on m.username = t.name";

위 쿼리는 Member에 Team을 조인하되, on 절을 이용해서 둘의 이름이 같은 경우에만 조인을 한다.

결과

예시) 

 다음은 member와 team을 조회하되, on문을 이용해 member.team의 이름이 "teamA"인 member와 team을 조인하는 코드이다.

package jpql;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

public class JpqlMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
        	// teamA
            Team teamA = new Team();
            teamA.setName("teamA");
            // teamA에 member 주입
            for (int i = 0; i < 3; i++) {
                Member member = new Member();
                member.setUsername("member" + i);
                member.setAge(i);
                member.changeTeam(teamA);
            }
            em.persist(teamA);
			
            // teamB
            Team teamB = new Team();
            teamB.setName("teamB");
            // teamB에 member 주입
            for (int i = 0; i < 3; i++) {
                Member member = new Member();
                member.setUsername("member" + i);
                member.setAge(i);
                member.changeTeam(teamB);
            }
            em.persist(teamB);
			
            // teamC
            Team teamC = new Team();
            teamC.setName("teamC");
            // teamC에 member 주입
            for (int i = 0; i < 3; i++) {
                Member member = new Member();
                member.setUsername("member" + i);
                member.setAge(i);
                member.changeTeam(teamC);
            }
            em.persist(teamC);


            List<Object[]> result =  
            	// 여기서 left join 안하고 그냥 join하면 조인이 안되어 null이 나오는 team 엔티티 때문에 에러 발생함.
                    em.createQuery("select m,t from Member m left join m.team t on t.name = 'teamA'")
                            .getResultList();

            for (int i = 0; i < 9; i++) {
                Object[] objects = result.get(i);
                System.out.println("member = " + objects[0] + " team = " + objects[1]);
            }

            tx.commit();
        } catch (Exception e) {
            tx.rollback(); // 오류 발생 시 롤백
            e.printStackTrace(); // 에러 내용 출력
        } finally {
            em.close(); // 종료
        }
        emf.close();
    }
}

출력

member = Member{id=2, username='member0', age=0} team = Team{id=1, name='teamA'}
member = Member{id=3, username='member1', age=1} team = Team{id=1, name='teamA'}
member = Member{id=4, username='member2', age=2} team = Team{id=1, name='teamA'}
member = Member{id=6, username='member0', age=0} team = null
member = Member{id=7, username='member1', age=1} team = null
member = Member{id=8, username='member2', age=2} team = null
member = Member{id=10, username='member0', age=0} team = null
member = Member{id=11, username='member1', age=1} team = null
member = Member{id=12, username='member2', age=2} team = null

 보면 select m,t를 했으므로 member와 team이 모두 조회되었지만, 그중 on문의 조회 조건대로 m.team의 name이 "teamA"인 member만 조인된 것을 볼 수 있다.

where과 on의 차이점

  • where는 엔티티 조회 조건을 지정하는데 사용한다.
  • on은 조인되는 엔티티 간의 조건을 지정하는데 사용한다.
// where: 조회하는 member 엔티티 중 id가 10보다 큰 엔티티를 조회하라
String query = "select m from Member m where m.id >10";
// on: member를 조회하는데 단, member.team의 id가 10보다 큰 엔티티들을 조인한다.
String query = "select m from Member m left join m.team t on m.username = t.name";

 

Join은 연관관계 없는 엔티티들을 한번에 가져올 때 사용할 수 있다.

package jpql;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
import java.util.Scanner;

public class JpqlMain {
    static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            for (int i = 0; i < 3; i++) {
                Member member = new Member();
                member.setUsername(""+i);
                member.setAge(i);
                em.persist(member);

                Team team = new Team();
                team.setName(""+i);
                em.persist(team);

            }
            
            List<Object[]> resultList = em.createQuery("select m, t from Member m left join Team t on m.username = t.name").getResultList();
            for (Object[] o: resultList) {
                System.out.println("member = " + o[0]);
                System.out.println("team = " + o[1]);
            }  //Member와 Team이 연관관계는 없어도 join을 통해서 같이 가져옴
            
            List<Member> resultList2 = em.createQuery("select m from Member m left join Team t on m.username = t.name",Member.class).getResultList();
            for (Member m: resultList2) {
                System.out.println("m.getTeam() = " + m.getTeam());
            } //m.getTeam() == Null => 연관관계 없음
            
            tx.commit();
        } catch (Exception e) {
            tx.rollback(); // 오류 발생 시 롤백
            e.printStackTrace(); // 에러 내용 출력
        } finally {
            em.close(); // 종료
        }
        emf.close();
    }
}


출력>>>
Hibernate: 
    /* select
        m,
        t 
    from
        Member m 
    left join
        Team t 
            on m.username = t.name */ select
                member0_.id as id1_0_0_,
                team1_.id as id1_3_1_,
                member0_.age as age2_0_0_,
                member0_.TEAM_ID as team_id5_0_0_,
                member0_.type as type3_0_0_,
                member0_.USERNAME as username4_0_0_,
                team1_.name as name2_3_1_ 
        from
            Member member0_ 
        left outer join
            Team team1_ 
                on (
                    member0_.USERNAME=team1_.name
                )
member = Member{id=1, username='0', age=0}
team = Team{id=2, name='0'}
member = Member{id=3, username='1', age=1}
team = Team{id=4, name='1'}
member = Member{id=5, username='2', age=2}
team = Team{id=6, name='2'}
Hibernate: 
    /* select
        m 
    from
        Member m 
    left join
        Team t 
            on m.username = t.name */ select
                member0_.id as id1_0_,
                member0_.age as age2_0_,
                member0_.TEAM_ID as team_id5_0_,
                member0_.type as type3_0_,
                member0_.USERNAME as username4_0_ 
        from
            Member member0_ 
        left outer join
            Team team1_ 
                on (
                    member0_.USERNAME=team1_.name
                )
m.getTeam() = null
m.getTeam() = null
m.getTeam() = null

'Spring > JPA' 카테고리의 다른 글

JPA- 조건식의 사용  (0) 2023.06.18
JPA-타입 표현  (0) 2023.05.20
JPA- 페이징  (0) 2023.05.13
JPA- 프로젝션  (0) 2023.05.12
JPA- 기본 문법과 쿼리 API  (0) 2023.05.07