[JPQL] select t fromTeam 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'
컬렉션 Fetch Join 사용 코드
1 2 3 4 5 6 7 8 9
String jpql = "select t from Team t join fetch t.members where t.name = '팀A'" List<Team> teams = em.createQuery(jpql, Team.class).getResultList(); for(Team team : teams) { System.out.println("teamname = " + team.getName() + ", team = " + team);
for (Member member : team.getMembers()) { //Fetch Join으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함 System.out.println(“-> username = " + **member.getUsername()**+ ", member = " + member); }
결과
teamname = 팀A, team = Team@0x100
-> username = 회원1, member = Member@0x200 -> username = 회원2, member = Member@0x300 teamname = 팀A, team = Team@0x100 -> username = 회원1, member = Member@0x200 -> username = 회원2, member = Member@0x300
Fetch Join과 DISTINCT
SQL의 DISTINCT는 중복된 결과를 제거하는 명령
JPQL의 DISTINCT 2가지 기능 제공
SQL에 DISTINCT를 추가
애플리케이션에서 엔티티 중복 제거
Fetch Join과 DISTINCT
1 2 3
**select distinct t fromTeam t join fetch t.members where [t.name](http://t.name/) = ‘팀A’**
SQL에 DISTINCT를 추가하지만 데이터가 다르므로 SQL 결과에서 중복제거 실패
Fetch Join과 DISTINCT
DISTINCT가 추가로 애플리케이션에서 중복 제거시도
같은 식별자를 가진 Team 엔티티 제거
[DISTINCT 추가시 결과]
teamname = 팀A, team = Team@0x100
-> username = 회원1, member = Member@0x200 -> username = 회원2, member = Member@0x300
하이버네이트6 변경 사항
DISTINCT가 추가로 애플리케이션에서 중복 제거 시도
-> 하이버네이트6 부터는 DISTINCT 명령어를 사용하지 않아 도 애플리케이션에서 중복 제거가 자동으로 적용됩니다.
Fetch Join과 일반 조인의 차이
일반 조인 실행 시 연관된 엔티티를 함께 조회하지 않음
1 2 3 4 5 6 7 8 9 10
[JPQL] select t fromTeam 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'
Fetch Join과 일반 조인의 차이
JPQL은 결과를 반환할 때 연관 관계 고려X
단지 SELECT 절에 지정한 엔티티만 조회할 뿐
여기서는 팀 엔티티만 조회하고, 회원 엔티티는 조회X
Fetch Join과 일반 조인의 차이
Fetch Join을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
Fetch Join은 객체 그래프를 SQL 한번에 조회하는 개념
Fetch Join 실행 예시
Fetch Join은 연관된 엔티티를 함께 조회함
1 2 3 4 5 6 7 8 9 10 11 12
[JPQL] select t fromTeam 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'
Fetch Join의 특징과 한계
Fetch Join 대상에는 별칭을 줄 수 없다.
하이버네이트는 가능, 가급적 사용X
둘 이상의 컬렉션은 Fetch Join 할 수 없다.
컬렉션을 Fetch Join하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다.
일대일, 다대일 같은 단일 값 연관 필드들은 Fetch Join해도 페이징 가능
하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험)
Fetch Join의 특징과 한계
연관된 엔티티들을 SQL 한 번으로 조회 - 성능 최적화
엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함
//글로벌 로딩 전략
@OneToMany(fetch = FetchType.LAZY)
실무에서 글로벌 로딩 전략은 모두 지연 로딩
최적화가 필요한 곳은 Fetch Join 적용
m.username 하는 식으로 사용 X
기본적으로 Fetch Join은 결과 전부를 가져오는 것이기에 사용 불가
일부만 끌어오려면 다른 Join을 사용하는 것이 좋다
Fetch Join - 정리
모든 것을 Fetch Join으로 해결할 수 는 없음
Fetch Join은 객체 그래프를 유지할 때 사용하면 효과적
여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면, Fetch Join 보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적
JPQL - 다형성 쿼리
중요도 - 하
TYPE
조회 대상을 특정 자식으로 한정
예) Item 중에 Book, Movie를 조회해라
1 2 3 4 5 6 7
[JPQL] select i fromItem i where type(i) IN (Book, Movie)
[SQL] select i from i where i.DTYPEin (‘B’, ‘M’)
TREAT(JPA 2.1)
자바의 타입 캐스팅과 유사
상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용
FROM, WHERE, SELECT(하이버네이트 지원) 사용
TREAT(JPA 2.1) 추가 설명
예) 부모인 Item과 자식 Book이 있다.
1 2 3 4 5 6 7
[JPQL] select i fromItem i where treat(i asBook).author = ‘kim’
[SQL] select i.* fromItem i where i.DTYPE = ‘B’ and i.author = ‘kim’
JPQL - 엔티티 직접 사용
JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용
1 2 3 4 5 6
[JPQL] select **count([m.id](http://m.id/))** from Member m //엔티티의 아이디를 사용 select **count(m)** fromMember m //엔티티를 직접 사용
[SQL](JPQL 둘다 같은 다음 SQL 실행) select count([m.id](http://m.id/)) as cnt from Member m
엔티티 직접 사용 - 기본 키 값
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// 엔티티를 파라미터로 전달 String jpql = “select m fromMember m where m = :member”; List resultList = em.createQuery(jpql) .setParameter("member", member) .getResultList();
// 식별자를 직접 전달 String jpql = “select m fromMember m where m.id = :memberId”; List resultList = em.createQuery(jpql) .setParameter("memberId", memberId) .getResultList();
//실행된 SQL select m.* fromMember m where m.id=?
엔티티 직접 사용 - 외래 키 값
1 2 3 4 5 6 7 8 9 10 11 12 13
Team team = em.find(Team.class, 1L); String qlString = “select m fromMember m where m.team = :team”; List resultList = em.createQuery(qlString) .setParameter("team", team) .getResultList();
String qlString = “select m fromMember m where m.team.id = :teamId”; List resultList = em.createQuery(qlString) .setParameter("teamId", teamId) .getResultList();
// 실행된 SQL select m.* fromMember m where m.team_id=?
JPQL - Named 쿼리
Named 쿼리 - 정적 쿼리
미리 정의해서 이름을 부여해두고 사용하는 JPQL
정적 쿼리
어노테이션, XML에 정의
애플리케이션 로딩 시점에 초기화 후 재사용
**애플리케이션 로딩 시점에 쿼리를 검증 *****
Named 쿼리 - 어노테이션
1 2 3 4 5 6 7 8 9 10 11 12
@Entity @NamedQuery( name = "Member.findByUsername", query="select m from Member m where m.username = :username") public classMember { ... }
<?xml version="1.0" encoding="UTF-8"?> <entity-mappingsxmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"version="2.1"> <named-queryname="Member.findByUsername"> <query><![CDATA[ select m from Member m where m.username = :username ]]></query> </named-query> <named-queryname="Member.count"> <query>select count(m) from Member m</query> </named-query> </entity-mappings>