게시글을 하나로 통합할까 하다가, 이론적인 부분과 실제 사용하는 구문을 분리하는 게 좋겠다는 생각이 들어서 두 개로 나눴다.
이 게시글은 JPQL 의 개념에 대한 내용이 주가 되었으니 참고 바란다.
1. JPQL 이 왜 필요한가?
JPA는 엔티티 객체를 중심으로 개발한다. 이렇게 엔티티 객체를 대상을 검색하게 되면 데이터 검색 시에도 테이블이 아닌 엔티티 객체를 대상으로 검색해야 한다.
애플리케이션에서, 검색 시 필요한 데이터만 DB에서 불러 오려면 결국 검색조건이 포함된 SQL 이 필요한데 이때 JPA 에서 SQL 을 추상화한 JPQL 이라는 객체 지향 쿼리 언어를 제공한다. JPQL을 사용하면 엔티티 객체 대상으로 쿼리를 실행해서 원하는 데이터를 뽑아올 수 있다!
- 참고 - SQL 은 DB 에서 테이블을 기준으로 데이터를 조회하는 언어이다.
2. JPQL 을 사용해 보자
JPQL 은, 엔티티 객체를 대상으로 조회하기 때문에 SQL 문과 유사하지만 From 뒤에 존재하는 객체명이 와야 한다.
간단한 예시는 다음과 같다.
em.createQuery (
"select m From Member m where m.username like '%kim%'",
Member.class
).getResultList();
기존에 MyBatis를 사용하던 입장에서, 이렇게 사용하게 되면 문법적으로 다른 점은 크게 없지만 동적쿼리를 짤 때 굉장히 불편하겠다고 생각했다.
그래서 hibernate에서는 대안을 제공하는데, 바로 criteria 다.
- criteria 사용하기
CriteriaBuilder cb = em.getCriteriaBuilder();
criteriaQuery<Member> query = cb.createQuery(Member.class);
Root<Member> m = query.from(Member.class)
query.select(m).where(cb.equal(m.get("username"), "kim"));
em.createQuery(cq).getResultList();
query.select(m). where(cb.equal(m.get("username"), "kim"));
코드를 보면 username 항목이 kim 인 사용자를 검색하고 있음을 알 수 있다. 공식적으로 제공하기도 하고, 기본 JPQL 보다는 동적 쿼리를 편하게 짤 수 있지만 criteria 도 사실 문법면에서 굉장히 불편하다.
- 네이티브 sql 사용하기
em
.createNativeQuery("select MEMBER_ID, city, street, zipcode, username from Member")
.getResultList();
또 다른 방법으로는 네이티브 SQL 이 있다. 다음과 같이 실제 SQL을 대입해서 사용이 가능하다.
JPQL 사용 시 주의해야 할 것들
- 엔티티이름을 사용한다. 테이블 이름이 아니다. ( @Entity에 name을 직접 지정할 수도 있지만 일반적으로는 클래스이름을 사용한다. )
- 별칭은 필수다. ( as는 생략 가능 )
typequery와 query
- typequery : 반환타입이 명확할 때 사용
TypedQuery<Member> query =
em.createQuery("SELECT m FROM Member m", Member.class);
- query : 타입정보가 명확하지 않을 때 사용
Query query =
em.createQuery("SELECT m.username, m.age from Member m");
JPQL에서 select 절에 조회할 대상을 지정하는 것을 의미하는 단어로 프로젝션이라는 단어를 사용한다.
프로젝션 종류에는 엔티티프로젝션, 임베디드 타입 프로젝션, 스칼라 타입 프로젝션이 있다.
- 엔티티 프로젝션
1) Member에 있는 모든 항목(필드/속성) 조회하기 : select m from Member m
2) Member 에 있는 항목 중 Team 만 조회하기 : select m.team from Member m
List<Member> result = em.createQuery("select m from Member m", Member.class)
.getResultList();
List<Team> result = em.createQuery("select m.team from Member m", Team.class)
.getResultList();
위 코드에서 m.team을 가져올 때, 실제 sql 쿼리 조회 시 Team 이랑 Member 랑 조인이 발생하게 된다. 결과는 원하는 값이 출력되겠지만 사실 이러한 코드는 좋지 않은 코드다. 왜냐하면 내부적으로 조인이 발생한다는 게 직관적으로 보이지 않기 때문이다. ( 이런 조인을 묵시적 조인이라고 한다. 묵시적 조인과 반대되는 조인은 명시적 조인으로, 실무에선 명시적 조인을 사용하도록 하자 )
그렇다면 어떻게 하는 게 좋을까? 내부적으로 조인이 발생하니, 조인을 사용해서 작성해 주면 된다.
다음 코드를 보자.
List<Team> result = em.createQuery("select t from Member m join m.team t", Team.class)
.getResultList();
select m.team from Member m으로 조회하든 select t from Member m join m.team t으로 조회 하든 결과 값은 동일하지만 코드를 작성할 때 어떻게 쿼리가 나갈지 예측되게 구현해야 한다.
- 임베디드 타입 프로젝션
1) Member의 address 항목 조회 하기 : select m.address from Member m
이번에는 address를 조회해보자. 엔티티의 주인으로부터 조회해야 한다.
Member 가 엔티티의 주인이 되므로 select m from Address m 은 사용 시 오류가 난다. ( 여기를 누르면 임베디드 관련 게시글을 볼 수 있습니다. )
실제로 select m from Address m을 하게 되면 다음과 같이 실행된다.
em.createQuery("select m from Address m", Address.class)
.getResultList();
- 실제 오류 내용
java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Address is not mapped [select m from Address m]
- 스칼라 타입 프로젝션
1) Member 항목 중 username과 age 조회하기 : select m.username, m.age from Member m1
스칼라 타입 프로젝션은 일반 SQL과 가장 유사한 프로젝션이다. 참고로 데이터 조회 시 앞에 distinct를 넣으면 중복 제거를 사용할 수 있다. ( select distinct m.username, m.age from Member m1 )
- 여러 값을 조회하는 프로젝션
username, age를 조회한 것처럼 프로젝션은 한 번에 값을 조회할 수 있다. 이때 각각 Query 타입,
Object [] 타입, new 명령어를 통한 조회 방법이 있다.
new 명령어를 사용하면, 엔티티 항목이 아니라 원하는 DTO 형태로 감싸서 조회할 수 있는데, 예시는 다음과 같다.
// dto 로 new 생성자를 써서 출력하는 방법
List<MemberDTO> resultList = em.createQuery(" select new jpql.MemberDTO(m.username, m.age) from Member m ").getResultList();
MemberDTO memberDTO = resultList.get(0);
3. 동적쿼리가 불편하다면? 대안으로 QueryDSL 사용을 사용해 보자.
위에서 간단한 조건임에도 불구하고, JPQL과 criteria를 사용하면 굉장히 불편하다는 것을 알 수 있다. 이럴 때 등장한 것이 QueryDSL이다. QueryDSL의 장점은 다음과 같다.
장점
- 문자가 아닌 자바코드로 jpql을 작성할 수 있다
- JPQL에 대한 빌더 역할을 해준다
- 컴파일 시점에 문법 오류를 발견할 수 있다
- 동적 쿼리 작성이 편리하다
사실 실제 개발해 본 입장에서 동적 쿼리는 다른 것 보다 MyBatis 가 압도적으로 편리했다. 그럼에도 불구하고 내가 가장 큰 메리트를 느낀 부분은 컴파일 시점에 문법 오류를 발견할 수 있다는 점이었다.
다음글은 여기로
2024.02.17 - [개발/jpa] - JPQL 의 개념과 간단한 사용 방법 ( 2 / 2 )
'Dev > JPA' 카테고리의 다른 글
org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [1] did not match expected type [java.lang.String (n/a)] 오류 해결하기 (0) | 2024.02.25 |
---|---|
JPQL 의 개념과 간단한 사용 방법 ( 2 / 2 ) (1) | 2024.02.17 |
JPA 데이터 타입 분류 (2) | 2024.02.15 |
영속성 전이와 고아 객체 (0) | 2024.02.06 |
JPA 에서 프록시 객체와 지연 로딩 (1) | 2024.02.05 |