-
JPA > JPQL, 프로젝션BackEnd/JPA 2021. 4. 27. 19:11
JPA 를 사용할 때 EntityManager.find() 를 사용하면 엔티티 하나를 조회할 수 있다. 하지만 특정 조건이 있는 모든 객체를 검색하고자 할 때는 보다 복잡한 검색 방법이 필요하다.
JPQL
테이블이 아닌 엔티티 객체를 대상으로 검색하는 객체지향 쿼리
SQL 을 추상화해서 특정 벤더에 의존적이지 않다.
JPA 는 JPQL 을 분석해 SQL 을 생성하고 실행한다.JPQL 은 아래와 같은 형태로 작성된다.
select m from Member as m where m.age > 18
1. 대소문자 구분
Member 와 age 같은 엔티티와 속성은 대소문자를 구분한다.
2. 엔티티 이름
Member 는 클래스 명이 아니라 엔티티 명이다. 엔티티명은 @Entity(name="") 으로 지정할 수 있다. 엔티티 명을 지정하지 않으면 클래스명이 기본값이다.
3. 별칭은 필수
as "m" 과 같은 별칭은 필수적이며 as 는 생략 가능하다.
m.age 에서 age 는 반드시 엔티티에 있는 변수명으로 작성해야한다.
JPQL 을 실행하려면 쿼리 객체를 만들어야한다. 쿼리객체에는 TypeQuery 와 Query 가 있다.
TypeQuery 와 Query
TypeQuery 는 반환 타입이 명확할 때,
Query 는 반환타입이 명확하지 않을 때 사용한다.
예를 들어 String 타입만 반환할 때는 TypeQuery 를
TypedQuery<String> query = em.createQuery("SELECT m.name FROM Member m", String.class);
여러 엔티티와 컬럼을 선택해야 할 때, 아래 예제처럼 String 와 Int 변수를 함께 반환할 때는 Query를 사용한다.
Query 객체는 여러 타입을 반환할 때는 Object[] 를 , 하나의 타입일 때는 Object 를 반환한다. Object 는 타입 변환이 필요하므로, 하나만 있을 때는 TypeQuery 를 쓰는 것이 낫다.
Query query = em.createQuery("SELECT m.username, m.age from Member m");
결과 조회를 하기 위해서는 아래 메서드를 호출한다.
query.getResultList() : 결과가 없으면 빈 컬렉션을 반환한다.
query.getSingleResult(); 결과가 하나일 때 사용한다.결과가 없으면 NoResultException 이 발생하고, 1개보다 많으면 NonUniqueResultException 이 발생한다.
파라미터 바인딩
JDBC 는 위치 기준 파라미터 바인딩만 지원하지만, JPQL 은 이름 기준 파라미터 바인딩도 지원한다.
1. 이름기반 파라미터
이름 기반 파라미터는 파라미터 값이 달라도 같은 쿼리로 인식되어 SQL 이 재사용된다.
String username = "aa"; TypedQuery<Member> q = em.createQuery("select m from Member m where m.username= :username", Member.class); q.setParameter("username",username); List<Member> result = q.getResultList();
2. 위치기반 파라미터
위치기반 파라미터 사용은 SQL Injection 공격에 취약하고, SQL 재사용이 되지 않아 이름 기반보다 성능이 떨어진다.
String username = "aa"; List<Member> result = em.createQuery("select m from Member m where m.username= ?1", Member.class); .setParameter("username",username); .getResultList();
프로젝션
프로젝션은 SELECT 절에 조회할 대상을 지정하는 것을 말한다.
엔티티 프로젝션, 임베디드 타입 프로젝션, 스칼라 타입 프로젝션이 있다. 스칼라는 숫자, 문자 등 기본 타입을 말한다.
SELECT m FROM Member m // Member 에서 멤버조회 > 엔티티 프로젝션 SELECT m.team FROM Member m //Member 에서 Team(Fk) 조회 > 엔티티 프로젝션 SELECT m.address FROM Member m //Member 에서 Address 값 타입 객체 조회 > 임베디드 타입 프로젝션 SELECT m.username, m.age FROM Member m //Member 의 primitive 값 타입 조회 > 스칼라 타입 프로젝션
위에서 Member 객체에서 Team 객체 정보를 가져오기 위한 두 번째 쿼리에서는 자동으로 조인문이 생성된다. (묵시적)
하지만 가독성, 유지보수를 용이하게 하기 위해서 SQL 문과 유사하게 쿼리를 작성하는 것이 좋다. (명시적)
SELECT m.team FROM Member m => SELECT t from Member m join m.team t
1. 엔티티 프로젝션
조회된 엔티티는 영속성 컨텍스트에서 관리된다.
2. 임베디드 프로젝션
임베디드 타입은 조회의 시작점이 될 수 없다는 제약이 있다.
예를 들어 "SELECT a FROM Address a"; 은 잘못된 쿼리이다.
"SELECT o.address FROM Order o" 처럼 사용해야한다.
3. 여러값 조회와 new 명령어
프로젝션으로 여러값을 조회하는 방법에는 세 가지가 있다.
여러값을 조회하기 위해서는 TypeQuery 는 사용 불가하고 Query 타입만 사용할 수 있다.
Query 타입은 Object[]를 반환해 아래처럼 사용할 수 있지만 실무에서는 DTO 를 생성해 사용하는 방법을 사용한다.
1. Object[] 배열 조회
List resultList = em.createQuery("select m.userName, m.age from Member m").getResultList(); Object o = resultList.get(0); Object[] result = (Object[]) o; System.out.println("result[0] = " + result[0]); System.out.println("result[0] = " + result[1]);
2. 조회용 DTO 를 생성
new DTO 를 select 할 때 DTO의 패키지명을 포함한 전체 클래스명을 입력해야하며,
생성자의 매개변수와 쿼리 안에 변수명 순서를 동일하게 맞춰야한다.
패키지명이 길어지면 작성하고 읽는데 불편함이 오는데 QueryDSL 을 사용하면 이런 불편함도 극복할 수 있다.
List<MemberDTO> resultList = em.createQuery("select new jpql.MemberDTO(m.userName, m.age) from Member m", MemberDTO.class) .getResultList();
public class MemberDTO { private String userName; private int age; public MemberDTO(String username, int age){//..} //.. }
페이징 API
페이징 처리용 SQL 관련된 메서드는 아래와 같다.
setFirstResult(10) : 조회 시작 위치를 명시한다. SQL 의 offset
setMaxResult(10); 조회할 데이터 수를 명시한다. SQL 의 limit
JPQL 조인
JPQL 조인의 특징은 연관 필드를 사용한다는 것이다. 연관필드는 다른 엔티티와 연관관계를 가지기 위해 사용하는 필드를 말한다.
List resultList = em.createQuery("select m from Member m INNER JOIN m.team t + "WHERE t.name = :teamName"); .getResultList();
위 코드는 아래와 같이 SQL 문에서 자동으로 Memeber 의 TeamID 와 Team 의 ID 를 조인 조건으로 만들어준다.
SELECT.. FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID=T.ID WHERE T.NAME=?
잘못된 사용 -SQL 처럼 작성하면 문법오류가 난다.
from Member m JOIN Team t
세타 조인
where 절을 사용해 전혀 관계없는 엔티티를 조회할 수 있다. Inner Join 만 지원한다.
아래 Member 의 username 과 Team 의 name 은 서로 관계가 없는 필드인데 조인해서 사용하고자 할 때 세타 조인을 할 수 있다.
이 경우 SQL 의 CROSS JOIN 이 발생한다.
//JPQL select count(m) from Member m, Team t where m.username=t.name; //SQL select count(m.id) from Member m cross join Team t where m.username=t.name;
JOIN ON 절 (JPA 2.1)
On 은 조인 대상을 필터링하고 조인한다. 내부 조인의 on 은 where 와 결과가 같아 보통 외부조인에서만 사용한다.
//JPQL select m,t from Member m left join m.team t on t.name='a'; //SQL select m.*,t.* from Member m left join Team t on m.team_id=t.id and t.name='a';
조인 시점에 team 을 name 으로 필터링 한 뒤 join 한다.
www.inflearn.com/course/ORM-JPA-Basic/dashboard
반응형'BackEnd > JPA' 카테고리의 다른 글
JPA > Fetch Join , BatchSize (0) 2021.04.27 JPA 경로표현식 (0) 2021.04.27 JPA > 값 타입 (0) 2021.04.25 JPA > 영속성 전이와 고아객체 (0) 2021.04.25 JPA > 연관관계 매핑 (0) 2021.04.18