1. 페치 조인(fetch join): 실무에서 정말정말 중요하다

sql의 조인 종류가 아니라 JPQL에서 성능 최적화를 위해 제공하는 기능이다 

연관된 엔티티나 컬렉션을 SQL에 한번에 함께 조회하는 기능이다

join fetch 명령어를 사용한다 

페치 조인 ::= [LEFT[OUTER]|[INNER] JOIN FETCH 조인경로

 

-엔티티 페치 조인

회원을 조회하면서 연관된 팀도 함께 조회한다(SQL로 한번에) 

SQL을 보면 회원 뿐만 아니라 팀(T.*)도 함께 SELECT 한다 

JPQL: select m from Member m join fetch m.team

SQL: select m.*,t.* from member m inner join team t on m.team_id =t.id

 

 

 

-컬렉션 페치 조인

일대다 관계에서 컬렉션 페치 조인을 사용한다 

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 m.team_id=t.id  where t.name='팀A'

 

 

 

-페치 조인과 DISTINCT

DISTINCT가 추가로 애플리케이션에서 중복 제거를 시도한다 

같은 식별자를 가진 Team 엔티티를 제거한다 

 

-페치 조인과 일반 조인의 차이

일반 조인 실행했을 때는 연관된 엔티티를 함께 조회하지 않는다 

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 m.team_id =t.id where t.name='팀A'

JPQL은 결과를 반환할 때 연관관계를 고려하지 않는다. 단지 select 절에 지정한 엔티티만 조회한다.

여기서는 팀 엔티티만 조회하고, 회원 엔티티는 조회하지 않는다 

 

페치 조인을 사용할 때만 연관된 엔티티도 함께 조회한다(즉시 로딩)

페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념이다

일대다 조인은 데이터가 뻥튀기 될 수 있다  

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'

 

-페치 조인의 특징과 한계 

 

1) 특징

연관된 엔티티들을 SQL 한번으로 조회한다. 성능이 최적화한다

엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선한다 

@OneToMany(fetch=FetchType.LAZY) //글로벌 로딩 전략 

실무에서 글로벌 로딩 전략은 모두 지연로딩

최적화가 필요한 곳은 페치 조인을 적용한다 

 

2)한계 

페치 조인 대상에는 별칭을 줄 수 없다. 하이버네이트에서는 가능 하지만, 가급적 사용하지 않는다 

페치 조인에서 컬렉션은 하나만 지정할 수 있다

둘 이상의 컬렉션은 페치 조인을 할 수 없다 

컬렉션을 페치 조인하면 페이징 API(setFirstResult,setMaxResults)를 사용할 수 없다.   

일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징이 가능하지만, 하이버네이트는 경고 로그를 남기고 메모리에서 페이징한다(매우 위험하다)

페치조인에 페이징을 쓰지 못하는데 사용해야하는 경우에는 

@BatchSize를 추가하거나 설정에서 batchsize 의존성을 추가한다 

 

 

=====> 정리

모든 것을 페치 조인으로 해결할 수는 없다

페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다 

여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야하면, 페치 조인 보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 dto로 반환하는 것이 효과적이다 

 

3가지 방법

  1. 엔티티를 페치조인으로 조회한다 그걸 쓴다
  2. 페치조인을 써서 애플리케이션에서 dto 바꿔서 뷰에 반환
  3. 처음부터 jpql   new operation으로 dto 반환해서 불러온다

 

2. 다형성 쿼리(TYPE,TREAT)

-TYPE

: type(i) in (a,b), 조회 대상을 특정 자식으로 한정한다 

예시) Item 중에 Book, Movie를 조회해라 

JPQL: select i from Item i where type(i) IN (Book,Movie)

SQL: select i from i where i.DTYPE in('B','M')

 

-TREAT

: Treat(parent as child), 자바의 타입 캐스팅과 유사하다 

상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용한다 

from,where,select(하이버네이트 지원)에서 사용한다 

예시) 부모인 Item과 자식 Book이 있다

JPQL: select i from Item i where treat(i as Book).author='Kim'

SQL: select i.* from Item i where i.DTYPE='B' and i.author='Kim'

 

3. JPQL- 엔티티 직접 사용 

 

-기본키 값:

JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본키 값을 사용한다 (엔티티 사용-> 엔티티 PK값 사용)

JPQL:

select count(m.id) from Member m //엔티티의 아이디를 사용

select count(m) from Member m // 엔티티를 직접 사용

SQL:  select count(m.id) as cnt from Member m 

 

 

4. Named 쿼리 

미리 정의해서 이름을 부여해두고 사용하는 JPQL이다 

정절 쿼리이며 어노테이션/XML에 정의한다

애플리케이션 로딩 시점에 초기화 후 재사용한다

애플리케이션 로딩 시점에 쿼리를 검증한다 

 

-Named 쿼리 환경에 따른 설정

xml이 항상 우선권을 가진다

애플리케이션 운영 환경에 따라 다른 xml을 배포할 수 있다 

 

5. 벌크 연산 

만약 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면? 

JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL을 실행해야한다 

( 재고가 10개 미만인 상품을 리스트로 조회한다 -> 상품 엔티티의 가격을 10% 증가 시킨다 -> 트랜잭션 커밋 시점에 변경감지가 동작한다)

이때 변경된 데이터가 100건이라면 100번의 UPDATE SQL을 실행해야한다 

 

 

-주의 

벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에서 직접 쿼리한다 

벌크 연산을 먼저 실행-> 영속성 컨텍스트를 초기화한다 

 

 

 

 

 

 

+ Recent posts