select m.username -> 상태 필드
from Member m
join m.team t -> 단일 값 연관 필드
join m.orders o -> 컬렉션 값 연관 필드
where t.name = '팀A'
select m.~~ From Member m
일 때
select t.members From Team t-->묵시적 조인 탐색 불가
select m.username From Team t join t.members m --> 명시적 조인과 별칭을 통해 탐색
묵시적 내부 조인 발생 = 엔티티영역에서는 편해보이지만 테이블영역에서는 조인일어남(운영하기 어렵다.)
실무에서는 무조건 명시적 조인으로 해야 쿼리 튜닝에도 편하다.
ex)
JPQL :select o.member from Order o
이라면
SQL :select m.* from Orders o inner join Member m on o.member_id = m.id
결론적으로 실무 Tip
1. 가급적 묵시적 조인 대신에 명시적 조인 사용
2. 조인은 SQL 튜닝에 중요 포인트
3. 묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어려움
페치 조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로
-회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에)
select m from Member m join fetch m.team
SELECT M.*, T.* FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID=T.ID
..
..
//회원1, 회원2에는 팀A 회원3에는 팀B에 넣고 회원과 팀을 다 조회하고 싶을 때
..
..
// 페치조인 없이 부른다면
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());
} // for
=====결과 요약설명=====
Member에 대해 select문 1번
Team에 대해 다른팀일 경우마다 n번으로 부르게된다.
username = 회원1, teamname = 팀A (SQL로 DB에서)
username = 회원2, teamname = 팀A(1차 캐시)
username = 회원3, teamname = 팀B (SQL로 DB에서)
위와 같은 방법을 해결하기 위해서는 페치조인 밖에 사용할 방법이 없다.
아래 부분으로 변경하면String jpql = "select m from Member m join fetch m.team";
조인된 결과로 깔끔하게 DB에서 프록시가 아닌 진짜 데이터로 지연로딩 전혀 없이 한번에 가져온다.
왜냐하면, 지연로딩을 해도 페치조인이 우선으로 먹히기 떄문에
select t
from Team t join fetch t.members
where t.name = ‘팀A'
SELECT T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'
String jpql = "select t from Team t join fetch t.members "
List<Team> teams = em.createQuery(jpql, Team.class).getResultList();
for(Team team : teams) {
System.out.println("team = " + team.getName() + "|members=" + team.getMembers().size());
}//for
=====결과=====
team = 팀A|members=2
team = 팀A|members=2
team = 팀B|members=1
(컬렉션 페치 조인 테이블)
SQL의 DISTINCT는 중복된 결과를 제거하는 명령
(SQL에 DISTINCT를 추가하지만 데이터가 다르므로 SQL 결과에서 중복제거 실패)
(PK까지 모든 데이터가 같아야 중복으로 확인하고 제거한다.)
ex)
JPQL의 DISTINCT 2가지 기능 제공
// DISTINCT가 추가로 애플리케이션에서 중복 제거시도
// 같은 식별자를 가진 Team 엔티티 제거
select distinct t
from Team t join fetch t.members
where t.name = ‘팀A’
// 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
페치 조인 예시)
// 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
Tip. 객체그래프란?
컴퓨터 과학에서 객체 지향 프로그램에서 객체 그룹은 다른 객체에 대한 직접 참조 또는 중간 참조 체인을 통해 서로 간의 관계를 통해 네트워크를 형성합니다. 이러한 객체 그룹을 그래프 이론에서 연구한 같은 이름의 수학적 객체를 따서 객체 그래프라고 합니다.
일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능
이유는 아까 봤던 데이터 뻥튀기 즉 DB영역에서의 데이터 중복과 영속성컨텍스트에서의 뜻이 달라서이다.
페이징 하게 되면 기존에 있던 테이블에 대해서 사이즈에 맞춰 자르게 되고 영속성 컨텍스트 입장에서는 엔티티와 테이블을 매핑 했을 때 자른 만큼 정보가 누락된다.
잘풀어서 설명 예시)
팀A에 ID값이 1,2인 회원을 페치 조인 하면
조인 테이블에 회원의 ID 값이 달라 2개의 정보가 들어간다.
여기서 사이즈를 1로 하게 되면 회원2의 정보는 잘리고
영속성 컨텍스트에서는 팀A에 대한 정보를 회원 1만 저장한다.
그럼 애플리케이션 영역에서 객체 그래프하고 정보가 안맞는다.
하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험)
페치 조인을 왜 사용할까?
목차에서 설명했었음 Tip. 조회의 방법 대표적 3가지
1. 엔티티를 패치조인을 사용하여 조회해서 그대로 사용한다.
2. 패치조인을 한 다음 애플리케이션에서 DTO로 바꿔 전달한다.
3. 처음 JPQL 짤 때부터 new operation DTO로 스위칭 하여 가져온다.
// JPQL
select i from Item i
where type(i) IN (Book, Movie)
--SQL
select i from i
where i.DTYPE in (‘B’, ‘M’)
// JPQL
select i from Item i
where treat(i as Book).auther = ‘koo’
--SQL
select i from i
where i.DTYPE = ‘B’ and i.auther = ‘koo’
// JPQL
select count(m.id) from Member m //엔티티의 아이디를 사용
select count(m) from Member m //엔티티를 직접 사용
--SQL (JPQL 둘다 같은 다음 SQL 실행)
select count(m.id) as cnt from Member m
String jpql = “select m from Member m where m = :member”;
List resultList = em.createQuery(jpql)
.setParameter("member", member)
.getResultList();
String jpql = “select m from Member m where m.id = :memberId”;
List resultList = em.createQuery(jpql)
.setParameter("member", memberId)
.getResultList();
select m.* from Member m where m.id=?
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name= "TEAM_ID")
private Team team
외래키가 아닌 엔티티 사용 할 때
Team team = em.find(Team.class, 1L);
String qlString = “select m from Member m where m.team = :team”;
List resultList = em.createQuery(qlString)
.setParameter("team", team)
.getResultList();
외래키를 통해 식별할 때
String qlString = “select m from Member m where m.team.id = :teamId”;
List resultList = em.createQuery(qlString)
.setParameter("teamId", teamId)
.getResultList();
쿼리문의 결과는 같다.
select m.* from Member m where m.team_id=?
<persistence-unit name="jpabook" >
<mapping-file>META-INF/ormMember.xml</mapping-file>
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
<named-query name="Member.findByUsername">
<query><![CDATA[
select m
from Member m
where m.username = :username
]]></query>
</named-query>
<named-query name="Member.count">
<query>select count(m) from Member m</query>
</named-query>
</entity-mappings>
@Entity
@NamedQuery(
name = "Member.findByUsername",
query="select m from Member m where m.username = :username")
public class Member {
...
}
List<Member> resultList =
em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username","회원1")
.getResultList();
String qlString = "update Product p " +
"set p.price = p.price * 1.1 " +
"where p.stockAmount < :stockAmount";
int resultCount = em.createQuery(qlString)
.setParameter("stockAmount", 10)
.executeUpdate();