[자바 ORM 표준 JPA] JPA 조인(JOIN)

[자바 ORM 표준 JPA] JPA 조인(JOIN)

JPA 조인(JOIN)


조인#


  • 내부 조인
SELECT m from Member m JOIN m.team t
SELECT m from Member m INNER JOIN m.team t

SQL과 비슷하지만 엔티티 중심으로 JPQL이 작성되며, Member의 Team 엔티티에 Alias를 t로 주어 조인 쿼리를 작성합니다.

  • 외부 조인
SELECT m from Member m LEFT JOIN m.team t
SELECT m from Member m LEFT OUTER JOIN m.team t

  • 세타 조인 연관관계가 없는 조인, 관계 없는 컬럼끼리의 조인
SELECT count(m) from Member m, Team t WHERE m.username = t.name

이전 소스#


src/main/java/jpql/domain/Member.java

Member.java
package jpql.domain;

import javax.persistence.*;


@Entity
public class Member {

    public Member(){
    }

    @Id @GeneratedValue
    private Long id;

    private String username;

    private int age;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team = new Team();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public jpql.domain.Team getTeam() {
        return team;
    }

    public void setTeam(jpql.domain.Team team) {
        this.team = team;
    }
    
    @Override
    public String toString() {
        return "Member{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

src/main/java/jpql/domain/Team.java

Team.java
package jpql.domain;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;


@Entity
public class Team {

    public Team() {
    }

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Member> getMembers() {
        return members;
    }

    public void setMembers(List<Member> members) {
        this.members = members;
    }
}

src/main/java/jpql/domain/Order.java

Order.java
package jpql.domain;

import javax.persistence.*;

@Entity
@Table(name = "ORDERS") //ORDER 가 예약어라 ORDERS로 테이블 명칭 지정
public class Order {
    public Order() {
    }

    @Id @GeneratedValue
    private Long id;

    private int orderAmount;

    @Embedded
    private Address orderAddress;

    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public int getOrderAmount() {
        return orderAmount;
    }

    public void setOrderAmount(int orderAmount) {
        this.orderAmount = orderAmount;
    }

    public Address getOrderAddress() {
        return orderAddress;
    }

    public void setOrderAddress(Address orderAddress) {
        this.orderAddress = orderAddress;
    }

    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }
}

src/main/java/jpql/domain/Address.java

Address.java
package jpql.domain;

import javax.persistence.Embeddable;
import java.util.Objects;

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;

    public String getCity() {
        return city;
    }

    private void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    private void setStreet(String street) {
        this.street = street;
    }

    public String getZipcode() {
        return zipcode;
    }

    private void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Address)) return false;
        Address address = (Address) o;
        return Objects.equals(getCity(), address.getCity()) && Objects.equals(getStreet(), address.getStreet()) && Objects.equals(getZipcode(), address.getZipcode());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getCity(), getStreet(), getZipcode());
    }

}

src/main/java/jpql/domain/Product.java

Product.java
package jpql.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Product {
    public Product() {
    }

    @Id @GeneratedValue
    private Long id;

    private String name;

    private int price;

    private int stockAmount;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public int getStockAmount() {
        return stockAmount;
    }

    public void setStockAmount(int stockAmount) {
        this.stockAmount = stockAmount;
    }
} 	

src/main/java/jpql/JpqlMain.java

JpqlMain.java
package jpql;

import jpql.domain.*;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpqlMain {
    //psvm 단축키로 생성 가능
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpql");
        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();

        tx.begin(); // [트랜잭션] 시작

        try{
		

        }catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }finally {
            em.close();
        }
        emf.close();
    }

}

내부 조인#


	String sQuery = "SELECT m FROM Member m INNER JOIN m.team t WHERE t.name = :teamName";
	String sQuery = "SELECT m FROM Member m JOIN m.team t WHERE t.name = :teamName"; // INNER는 생략 가능

Member.java - 조인을 하기전 양방향 관계 설정 및 연관관계 편의 매소드 생성

	...
	public void changeTeam(Team team){
        this.team = team;
        team.getMembers().add(this); // Team을 세팅할때, Team에도 현재 Member 추가
     }
	...

JpqlMain.java

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

            Member member1 = new Member();
            member1.setUsername("member");
            member1.setAge(30);
            member1.changeTeam(team);

            em.persist(member1);

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

            String sQuery = "SELECT m FROM Member m JOIN m.team t";
            List<Member> result = em.createQuery(sQuery, Member.class).getResultList();

            tx.commit();

console

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
Hibernate: 
    select
        team0_.id as id1_3_0_,
        team0_.name as name2_3_0_ 
    from
        Team team0_ 
    where
        team0_.id=?

Member와 Team이 Inner Join된 SQL이 출력된 것을 확인 할 수 있습니다. 하지만 사용하지 않은 Team의 SELECT 쿼리가 하나 더 출력된 것이 보입니다.

@ManyToOne N:1 관계에서는 꼭 fetch= FetchType.Lazy 로 설정해 두어야 합니다.

Member.java - fetch = FetchType.LAZY 지정

@ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;

다시 실행하면

console

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

Join 쿼리만 출력됩니다.
이후 t엔티티 객체를 사용할 수 있습니다. 예를 들면 t.name 을 파라미터로 조건을 걸어 쿼리를 조회할 수 있습니다.

            String teamName = "team1";
		   //String sQuery = "SELECT m FROM Member m INNER JOIN m.team t WHERE t.name = :teamName"; // INNER JOIN 으로도 사용가능
		   
            String sQuery = "SELECT m FROM Member m JOIN m.team t WHERE t.name = :teamName";
            List<Member> result = em.createQuery(sQuery, Member.class)
                    .setParameter("teamName",teamName)
                    .getResultList();

외부 조인#


SELECT m FROM Member m LEFT OUTER JOIN m.team t WHERE t.name = :teamName
SELECT m FROM Member m LEFT JOIN m.team t WHERE t.name = :teamName // OUTER는 생략가능

Jpql.java

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

            Member member1 = new Member();
            member1.setUsername("member");
            member1.setAge(30);
            member1.changeTeam(team);

            em.persist(member1);

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

            String teamName = "team1";

            // String sQuery = "SELECT m FROM Member m LEFT OUTER JOIN m.team t WHERE t.name = :teamName";
            String sQuery = "SELECT m FROM Member m LEFT JOIN m.team t WHERE t.name = :teamName";
            List<Member> result = em.createQuery(sQuery, Member.class)
                    .setParameter("teamName",teamName)
                    .getResultList();

            tx.commit();

console

Hibernate: 
    /* SELECT
        m 
    FROM
        Member m 
    LEFT JOIN
        m.team t 
    WHERE
        t.name = :teamName */ 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_ 
        left outer join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id 
        where
            team1_.name=?

세타 조인#


MEMBER와 TEAM을 모두 불러와 m.username과 t.name이 같은 것을 조회
연관관계가 없는 것을 조회

SELECT m FROM Member m, Team t  WHERE m.username = t.name

JpqlMain.java

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

            Member member1 = new Member();
            member1.setUsername("member");
            member1.setAge(30);
            member1.changeTeam(team);

            em.persist(member1);

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

            String teamName = "team1";

            // String sQuery = "SELECT m FROM Member m LEFT OUTER JOIN m.team t WHERE t.name = :teamName";
            String sQuery = "SELECT m FROM Member m, Team t  WHERE m.username = t.name";
            List<Member> result = em.createQuery(sQuery, Member.class)
                    .getResultList();

            tx.commit();

console

 /* SELECT
        m 
    FROM
        Member m,
        Team t  
    WHERE
        m.username = t.name */ 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_ cross  
        join
            Team team1_ 
        where
            member0_.username=team1_.name

cross join 이라고 나오는데 이것은 두개의 테이블을 곱해서 N*M의 결과를 보여주는 조인
이후 m.username=t.name 이 같은 결과만 나오게 조건을 적용

조인 - ON 절#


  • ON절을 활용한 조인 (JPA 2.1 부터 지원)
    1. 조인 대상 필터링
    2. 연관관계가 없는 외부 조인(하이버네이트 5.1 부터) 연관관계 없는 조인은 과거 INNER JOIN만 가능해서 실제 현직에서는 Native로 쿼리를 작성하는 경우도 있었다고합니다.

조인 대상 필터링#


예) 회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인

JPQL :

SELECT m, t FROM Member m LEFT OUTER JOIN m.team t on t.name = 'A'

SQL :

SELECT m.*, t.* FROM MEMBER m
                LEFT OUTER JOIN Team T
                  ON m.TEAM_ID = t.id
                  AND t.name = 'A'

JpqlMain.java

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

            Member member1 = new Member();
            member1.setUsername("member");
            member1.setAge(30);
            member1.changeTeam(team);

            em.persist(member1);

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

            String teamName = "team1";

            String sQuery = "SELECT m FROM Member m LEFT OUTER JOIN m.team t on t.name = :teamName";
            List<Member> result = em.createQuery(sQuery, Member.class)
                    .setParameter("teamName",teamName)
                    .getResultList();

console

Hibernate: 
    /* SELECT
        m 
    FROM
        Member m 
    LEFT OUTER JOIN
        m.team t 
            on t.name = :teamName */ 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_ 
        left outer join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id 
                and (
                    team1_.name=?
                )

연관관계 없는 엔티티 외부 조인#


예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인

JPQL :

SELECT m, t FROM
Member m LEFT JOIN Team on m.username = t.name

SQL :

SELECT m.*, t.* FROM
Member m LEFT JOIN Team t on m.username = t.name

세타 조인과 비슷하지만, LEFT OUTER JOIN에 on 절을 이용하여 연관관계가 없는 조인을 할 수 있습니다.

JpqlMain.java

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

            Member member1 = new Member();
            member1.setUsername("member");
            member1.setAge(30);
            member1.changeTeam(team);

            em.persist(member1);

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

            String sQuery = "SELECT m FROM Member m LEFT OUTER JOIN m.team t on t.name = m.username";
            List<Member> result = em.createQuery(sQuery, Member.class)
                    .getResultList();

            tx.commit();

console

Hibernate: 
    /* SELECT
        m 
    FROM
        Member m 
    LEFT OUTER JOIN
        m.team t 
            on t.name = m.username */ 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_ 
        left outer join
            Team team1_ 
                on member0_.TEAM_ID=team1_.id 
                and (
                    team1_.name=member0_.username
                )

참고#