[자바 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 부터 지원)
- 조인 대상 필터링
- 연관관계가 없는 외부 조인(하이버네이트 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
)