JPQL은 왜 사용하나?
우리는 DB에서 데이터를 조회할 때, em.find를 통해 쉽게 조회할 수 있다.
그런데 왜 JPQL을 사용할까?
예를 들어 member가 100명이 있는데 여기서 특정 나이 이상인 member를 조회하고 싶다.
그렇다면 우리가 할 수 있는 일은 모든 member 수 만큼 em.find를 하여,
각 member 객체의 age를 조회해야한다.
데이터의 개수가 더욱 더 늘어난다면 우린 DB에 있는 모든 member들을 모두 객체로 만들어야한다.
이 점이 불가능하기 때문에 검색 조건이 있는 JPQL을 사용하여 쉽게 조회할 수 있다.
JPQL의 특징
우리가 흔히 알고 있는 SQL은 DB 테이블을 대상으로 쿼리를 짠다.
하지만 JPQL은 엔티티 객체를 대상으로 쿼리를 작성한다.
또한, 앞서 말한 방언을 처리하는 것처럼 특정 DB SQL에 의존하지 않게 된다.
그래서 JPQL을 객체 지향 쿼리라고 한다.
Criteria
"select m From Member m where m.name like '%kim%'"
JPQL을 작성한 것인데, 우리는 여기서 kim 대신에 username을 넣어 동적 쿼리를 생성하고 싶다.
String username = "asdds";
if (username != null) {
String where = "where m.username like '%kim%";
qlString + where;
}
이렇게 코드를 작성하여 동적 쿼리를 할 수 있지만, +연산을 사용하면서 where 앞에 띄어쓰기를 하는 등
이런 곳에서 많은 버그가 많이 생긴다.
그래서 JPA 표준 명세에 나온 Criteria가 있다.
예시 코드는 책에 나와있다.
Criteria이 단점은 SQL스럽지가 않다. 따라서 표준 스펙에는 들어있지만 실무에서는 쓰지 않는다고 한다.
JPQL
반환 타입이 명확할 때 TypeQuery, 명확하지 않으면 Query를 사용한다.
em.createQuery("select m from Member as m");
Query query1 = em.createQuery("select m from Member as m");
뒤에 반환 타입이 없어 Query를 사용하는 것을 볼 수 있다.
em.createQuery("select m from Member as m", Member.class)
TypedQuery<Member> query = em.createQuery("select m from Member as m", Member.class);
Member.class라는 반환 타입이 있어 TypedQuery를 사용한다.
그렇다면 항상 반환 타입을 명시해서 TypedQuery를 작성하면 되지 않을까라는 생각을 할 수 있다.
그러나 아래와 같은 예시에서는 반환 타입을 명시할 수 없다.
Query query1 = em.createQuery("select m.username, m.age from Member as m");
username은 String이고 age는 int인데, 위와 같은 상황일 때 타입정보를 입력할 수 없다.
TypedQuery<String> query2 = em.createQuery("select m.username from Member as m", String.class);
이렇게 혼자 String이 있어서 반환 타입을 명시할 수 있게 되어, TypedQuery를 사용할 수 있다.
쿼리를 통하여 데이터를 조회할 때, 영속성 컨텍스트의 관리를 받게 될까?
List<Member> result = em.createQuery("select m from Member m", Member.class)
.getResultList();
Member findMember = result.get(0);
findMember.setAge(20);
여기서 쿼리를 통해 member를 조회하여 나이를 수정했다.
이 때, List에 있는 Member 객체들은 영속성 컨텍스트의 관리를 받을까?
Member의 age를 수정했더니, age가 20으로 수정됐다.
즉, 영속성 컨텍스트의 관리를 받는다는 것을 알 수 있다.
하지만 중요한 점이 있다.
em.createQuery("select o.address from Order o", Address.class)
.getResultList();
위의 코드는 임베디드 타입인 Address가 조회됐다.
이 때, Address는 엔티티 타입이 아니라 값 타입이므로 영속성 컨텍스트에서 관리되지 않는다.
JPQL과 SQL 통일성
List<Team> result = em.createQuery("select m.team from Member m", Team.class)
.getResultList();
ㅇMember의 Team을 조회하기 위해서는 join을 사용해야한다.
하지만 작성한 JPQL는 join이 없다.
우리는 JPQL을 보고도 어느 SQL이 나가는지 알아야하기 때문에
JPQL과 SQL을 최대한 맞춰주는 것이 좋다고 한다.
List<Team> result = em.createQuery("select t from Member m join m.team t", Team.class)
.getResultList();
똑같이 inner join을 사용한다.
프로젝션
프로젝션이란 SELECT 절에 조회할 대상을 지정하는 것이다.
조회할 대상은 크게 엔티티, 임베디드 타입, 스칼라 타입(기본 데이터 타입)이 있다.
엔티티는 위에서 했고, 임베디드 타입을 보자.
임베디드 타입
em.createQuery("select o.address from Order o", Address.class)
.getResultList();
Address는 Order 엔티티 안에 있는 임베디드 값 타입이므로 별다른 조인없이 select을 이용하여 조회할 수 있다.
스칼라 타입
em.createQuery("select m.username, m.age from Member m")
.getResultList();
위와 같이 username은 String, age는 int인데 어떻게 가져올 수 있을까?
방법은 3가지가 있다.
Query, Object[], new 로 조회할 수 있다.
Query
List resultList = em.createQuery("select m.username, m.age from Member m")
.getResultList();
System.out.println("resultList.size() = " + resultList.size());
for (Object o : resultList) {
Object[] result = (Object[]) o;
System.out.println(result[0]);
System.out.println(result[1]);
}
결과를 보다시피,
username과 age가 쌍으로 resultList에 저장이 되는 것 같다.
Object[]
List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m")
.getResultList();
Object[] result = resultList.get(0);
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
Object[] 방법과 Query 방법은 비슷한 것 같다.
다만 List에 타입을 명시해주어 Object[] 방법이 더욱 간단해 보인다.
new 명령어 사용
em.createQuery("select new jpql.MemberDto(m.username, m.age) from Member m", MemberDto.class)
.getResultList();
엔티티가 아닌 다른 타입을 명시할 경우 생성자를 호출하듯이 new를 이용하여 조회한다.
당연히 MemberDto 클래스에는 생성자가 있어야한다.
해당 방법은 패키지가 길어지면 경로를 모두 써야한다는 단점이 있다.
하지만 이 방법이 제일 깔끔하다고 한다.
Reference
자바 ORM 표준 JPA 프로그래밍 (김영한 지음)
'Spring 강의 > JPA - 기본편' 카테고리의 다른 글
객체지향 쿼리 언어2 (0) | 2022.01.05 |
---|---|
값 타입 (0) | 2021.12.12 |
프록시와 연관관계 관리(2) 즉시 로딩과 지연 로딩 ~ 영속성 전이 (0) | 2021.12.10 |
프록시와 연관관계 관리 (1) 프록시 (0) | 2021.12.10 |
고급 매핑 (0) | 2021.12.07 |