본문 바로가기

Spring/JPA

JPA Basics - JPQL Advanced

1. 경로 표현식

 ◍  경로표현식: .(점)을 찍어 객체 그래프를 탐색하는 것

select m.username -> 상태 필드
from Member m
   join m.team t. -> 단일 값 연관 필드
   join m.orders o -> 컬렉션 값 연관 필드
where t.name = '팀A'

 ◍  용어 정리

      ○ 상태필드(state field): 단순히 값을 저장하기 위한 필드(ex> m.username)

      ○ 연관필드(association field): 연관 관계를 위한 필드

           ◼  단일값 연관 필드: @ManyToOne, @OneToOne, 대상이 엔티티(ex> m.team)

           ◼  컬렉션 값 연관 필드: @OneToMany, @ManyToMany, 대상이 컬렉션(ex> m.orders)

 ◍  특징

      ○ 상대 필드 경로 탐색: 경로 탐색의 끝, 탐색 안함.

           ◼  JPQL: select m.username, m.age from Member m

           ◼  SQL: select m.username, m.age from Member m

      ○ 단일 값 연관 경로 탐색: 묵시적 내부 조인 발생, 탐색함

           ◼  JPQL: select o.member from Order o

           ◼  SQL: select m.* from Order o inner join Member m on o.member_id = m.id

      ○ 컬렉션 값 연관 경로: 묵시적 내부 조인 발생, 탐색 안함 -> FROM절에서 명시적 조인 통해 별칭 얻으면 별칭 통해 탐색 가능.

      ○ 명시적, 묵시적 조인

           ◼  명시적 조인: join 키워드 직접 사용

              ◻ select m from Member m join m.team t

           ◼  묵시적 조인: 경로 표현식에 의해 묵시적으로 SQL 조인 발생(내부 조인만 가능)

              ◻ select m.team from Member m

           ◼  경로 탐색을 사용한 묵시적 조인 시 주의 사항      

             ◻ 항상 내부 조인

             ◻ 컬렉션은 경로 탐색의 끝, 명시적 조인을 통해 별칭을 얻어야함

             ◻ 경로 탐색은 주로 SELECT, WHERE 절에서 사용하지만 묵시적 조인으로 인해 SQL의 FROM(JOIN) 절에 영향을 줌

  예제

           ◼ select o.member.team from Order o -> 성공

            select t.members frokm Team -> 성공

            select t.members.username from Team t -> 실패

            select m.username from Team t join t.members m -> 성공

             ◻ 

 ◍  실무조언

      ○  가급적 묵시적 조인 대신에 명시적 조인 사용

      ○  조인은 SQL 튜닝에 중요 포인트

      ○  묵시적 조인은 조인이 일어나는 상황을 한눈에 파악하기 어려움.

 

2. 페치조인

 ◍  페치 조인

      ○  SQL 조인 종류 아님

      ○  JPQL에서 성능 최적화를 위해 제공하는 기능

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

      ○  join fetch 명령어 사용

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

 ◍  엔티티 페치 조인

      - 회원을 조회 하면서 연관된 팀도 함께 조회. SQL보면 회원 뿐만 아니라 팀(T.*)도 함께 셀렉

      ○  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 T.id = M.team_id where T.name = '팀A'

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()) {
//페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println(“-> username = " + member.getUsername()+ ", member = " + member); }
}

 

◍  페치 조인과 DISTINCT

      ○  SQL의 DISTINCT는 중복 된 결과를 제거하는 명령

      ○  JPQL의 DISTINCT는 2가지 기능 제공

           ◼  SQL에 DISTINCT 추가

           -> select distint t from Team t join fetch t.members where t.name = '팀A'

           -1. SQL에서 DISTINCT추가 하지만 데이터 달라 SQL결과에서 중복 제거 실패

           -2. DISTINCT가 추가로 애플리케이션에서 중복 제거 시도 (같은 식별자를 가진 Team 엔티티 제거)

           --> 하이버네이트65 부터는 DISTINCT 명령어를 사용하지 않아도 애플리케이션에서 중복제거가 자동으로 적용.

           ◼  애플리케이션에서 엔티티 중복 제거

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

      ○  일반 조인 실행시 연관된 엔티티를 함께 조회 하지 않음

           ◼  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 WEHRE T.NAME = '팀A'

      ○  JPQL은 결과를 반환할 때 연관관계 고려하지 않음

      ○  단지 SELECT 절에 지정한 엔티티만 조회함.

      ○  여기서는 팀 에티티만 조회하고, 회원 엔티티는 조회안함.

 

      ○  페치 조인은 연관된 엔티티를 함께 조인함

           ◼  JPQL: select 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'      

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

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

 

 ◍  페치 조인의 특징과 한계

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

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

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

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

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

 

 ◍ 정리

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

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

      ○ 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 다른 결과를 내야하면, 페치 보다 일반 조인을 사용 & 필요한 데이터들만

       조회해 DTO로 반환 

3. 다형성 쿼리

 ◍  TYPE

      ○  조회 대상을 특정 자식으로 한정(예> 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 (예> 부모인 Item과 자식 Book이 있다.) 

        자바 타입의 캐스팅과 유사

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

      ○  FROM, WHERE, SELECT (하이버네이트 지원) 사용

           ◼  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'

 

4. 엔티티 직접 사용

 ◍  기본 키값  

      ○  JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용

           ◼  JPQL

              ◻ select count(m.id) from Membe 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("memberId", memberId) .getResultList();

 

      ○  실행된 SQL

select m.* from Member m where m.id=?

 

 ◍  외래 키값

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();

//실행된 SQL
select m.* from Member m where m.team_id=?

 

 

5. Named 쿼리

 ◍  정적 쿼리

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

      ○  정적 쿼리

      ○  어노테이션, XML에 정의

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

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

 ◍  어노테이션

@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();

 ◍  XML에 정의

[META-INF/persistence.xml]
<persistence-unit name="jpabook" >
<mapping-file>META-INF/ormMember.xml</mapping-file>

[META-INF/ormMember.xml]
<?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>

 ◍  Named쿼리 환경에 따른 설정

        XML이 항상 우선권을 가진다

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

 

6. 벌크 연산

 ◍ 기본 

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

      ○  JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL 실행 

           ◼  1. 재고가 10개 미만인 상품을 리스트로 조회

           ◼  2. 상품 엔티티의 가격을 10% 증가 한다

           ◼  3. 트랜잭션 커밋 시점에 변경감지가 동작

           -> 변경된 데이터가 100건이라면 100번의 UPDATE SQL 실행

      ○  예제

           ◼  쿼리 한번으로 여러 테이블 로우 변경(엔티티)

           ◼  executeUpdate()의 결과는 영향받은 엔티티 수 반환

           ◼  UPDATE, DELETE 지원

           ◼ INSERT  (insert into .. select, 하이버네이트 지원)

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();

 

◍  주의

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

           ◼  벌크 연산을 먼저 실행

           ◼  벌크 연산 수행 후 영속성 컨텍스트 초기화

 

'Spring > JPA' 카테고리의 다른 글

JPA Basic - JPQL Basic  (0) 2024.01.10
JPA Basic - 값 타입  (0) 2024.01.09
JPA Basic - 프록시, CASCADE, 고아객체  (0) 2024.01.09
JPA Basic - 상속관계 매핑  (0) 2024.01.09
JPA Basic - 엔티티 매핑  (0) 2024.01.09