JPA

10장 객체지향 쿼리 언어

iksadnorth 2023. 9. 4. 23:25

해당 게시물은 책 '자바 ORM 표준 프로그래밍'을 읽고 작성했습니다.

👣 개요

여태까지의 JPA로는 제한적인 쿼리만 수행할 수 있었다.
때문에 복잡한 검색 조건을 사용하기 위해 다양한 쿼리 기술을 지원한다.

JPQL, Criteria, QueryDSL 등등이 이것에 속한다.

 

👣 JPQL

JPQL은 SQL과 달리 테이블 중심적 언어가 아니라 객체 지향적 언어다.
SQL을 추상화해서 특정 벤더에 의존적이지 않아서 이식성이 좋다.

기본 문법
1. 대소문자 구분
2. 엔티티 이름 - Table 이름을 기입하는 것이 아닌 @Entity(name = "XXX")의 이름 속성을 사용한다.
3. 별칭 필수 - 'Member AS m' 과 같이 별칭을 꼭 사용해야 함.

TypeQuery 클래스
쿼리 내용을 담고 있는 클래스로서 특정 메서드를 호출하면 DB에 쿼리에서 필요한 정보를 가지고 온다.
1. getResultList() : 결과가 없으면 오류없이 빈 컬렉션을 반환함.
2. getSingleResutl() : 결과가 없으면 혹은 결과가 2개 이상이면 예외를 발생시킨다.

파라미터 바인딩
JPQL 쿼리에서 :xxx 와 같은 문구를 추가하면 해당 위치에 동적으로 쿼리를 만들 수 있다.

em.createQuery("select m from member m where m.id = :id")
  .setParameter("id", 1L);

프로젝션
Select 절에서 조회할 대상을 지정하는 것을 프로젝션이라고 한다.
1. 엔티티 프로젝션

em.createQuery("select m from member m", Member.class);

2. 임베디드 타입 프로젝션

@Embeddable
public class Location {
    private String dou;
    private String si;
    private String dong;
}
em.createQuery("select m.location from member m", Location.class);

3. 여러 값 조회

Query query = em.createQuery("select m.username, m.age from member m");
Object[] projections = query.getSingleResult();

4. new 명령어

em.createQuery("select new com.example.kr.UserDTO(m.username, m.age)", UserDTO.class);

 

👣 JPQL 조인

내부 조인
Inner Join라고 사용하지만 Join만 사용해도 무방하다.
기존의 SQL과 다른 점은 On절을 사용하지 않아도 되나 연관관계를 이용해서 Join해야 한다는 것이다.

em.createQuery(
    "select m from member m inner join m.team t where t.name = :teamName"
)

 

외부 조인
Left Outer Join라고 사용하지만 Left Join만 사용해도 무방하다.

em.createQuery(
    "select m from member m left join m.team t"
);

 

 

세타 조인
SQL의 Cross Join처럼 모든 행을 연결하는 대신 조건문으로 Join 효과를 낼 수 있음.

em.createQuery("
    select m from Member m, Team t where m.username = t.name
");

페치 조인
기존의 조인 방식으로는 항상 연관 관계를 규정하는 필드를 호출 시, 지연 로딩으로 데이터를 불러온다.
이것은 때에 따라 성능을 높일 수도 있지만 때로는 N+1 문제를 낳기도 한다.
때문에 기존의 SQL 처럼 한번 호출할 때, 한꺼번에 DB에서 로드하려고 한다면 페치 ㅗㅈ인을 사용해야 한다.

em.createQuery("
    select t from Team t join fetch t.members
");

경로 표현식
엔티티는 OOP 성질을 가지고 있기에 기존의 SQL과 달리 속성으로 그래프 탐색이 가능하다.
하지만 때때로 그래프 탐색을 하지 못하는 경우도 있기 때문에 주의해서 사용해야 한다.
아래 예시를 볼 때, name 필드는 당연히 그래프 탐색을 못한다.
예를 들어, m.name.firstName와 같은 탐색을 못한다.
하지만, 주목할 것은 컬렉션 연관 필드다. Order 엔티티의 createdAt 필드가 존재한다고 가정하자.
m.orders.createdAt으로 해당 필드를 검색할 수 있을거 같지만 그렇지 못하다.
오로지 정상적으로 탐색 가능한 필드는 단일 연관 필드다.

@Entity
public class Member {
    @Id
    private Long id;
    
    private String name; // 상태 필드
    
    private Team team; // 연관 필드 - 단일 값
    
    private List<Order> orders; // 연관 필드 - 컬렉션 값
}
select m.name from Member m
join m.team t
join m.orders o;

 

👣 JPQL 서브 쿼리

SQL 처럼 서브 쿼리를 지원하긴 하지만
Where, Having절에서만 사용할 수 있다는 특징이 있다.

select m from Member m
where m.age > (select avg(m2.age) from Member m2);

 

👣 JPQL Named 쿼리

기존의 방식처럼 JPQL을 문자로 완성해서 직접 넘기는 방법도 있지만
미리 정의한 쿼리에 이름을 부여해 필요할 때, 사용하는 방법도 있다.

이렇게 하면 App 로딩 시점에 문법을 체크하고 미리 파싱할 수 있다.
그리고 파싱된 결과를 재사용하므로 성능상 이점도 있다.

@Entity
@NamedQueries({
    @NamedQuery(
        name = "Member.findByUsername",
        query= "select m from Member m where m.username = :username"
    ),
    @NamedQuery(
        name = "Member.count",
        query= "select count(m) from Member m"
    ),
})
public class Member { ... }
em.createNamedQuery("Member.findByUsername", Member.class);

 

👣 QueryDSL

JPA Criteria도 컴파일 단계에서 오류를 잡을 수 없는 JPQL 작성에 도움을 주고 있으나
더 대중성 있고 쉽고 간결한 QueryDSL을 사용하는 것이 더 좋다.

 

QueryDSL

👣 개요 QueryDSL은 JPQL 언어를 그대로 작성함으로서 일어나는 단점을 커버하기 위해 생긴 기술이다. 해당 기술을 통해 런타임이 아닌 컴파일 단계에서 오류를 미리 잡아낼 수 있다는 특징이 있고

ikadnorth.tistory.com

 

 

👣 영속성 컨텍스트 vs JPQL

 

영속성 컨텍스트 vs JPQL

👣 개요 JPQL은 영속성 컨텍스트에서 미리 조회하고 DB에 쿼리를 보내지 않고 DB에 쿼리를 먼저 보낸 후에 영속성 컨텍스트를 재정비한다. JPQL의 경우, 영속성 컨텍스트를 활용하지 않기 때문에

ikadnorth.tistory.com

 

'JPA' 카테고리의 다른 글

영속성 컨텍스트 vs JPQL  (0) 2023.09.06
QueryDSL  (0) 2023.09.06
9장 값 타입  (0) 2023.09.03
8장 프록시와 연관관계 관리  (0) 2023.08.31
7장 고급 매핑  (0) 2023.08.31