-
JPQL 이란?프로그래밍 기초 공부 2023. 3. 30. 02:35
JPQL
- Java Persistence Query Language의 약자
- JPA의 일부로 정의된 플랫폼 독립적인 객체지향 쿼리 언어임
- JPA는 엔티티 객체를 중심으로 개발하기 때문에 SQL을 사용하지 않음
- 엔티티 객체를 대상으로 쿼리를 작성함
JPQL 특징
- JPQL은 ANSI 표준에서 지원하는 쿼리 명령문을 모두 제공
- ex) SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 등
- JPQL은 SQL을 추상화하여 객체 중심적, 객체 지향적 쿼리 언어
- 테이블이 아닌 객체를 대상으로 검색을 수행하는 쿼리 언어
- JPQL과 SQL을 구분할 수 있는 특성
- JPQL은 SQL을 추상화한 것으로, 특정 DB에 의존적 또는 종속적이지 않다.
- DB 사용에 있어서 자유롭다.
- JPA를 통해 작성된 JPQL은 SQL로 번역된다.
JPQL과 SQL 차이점
- JPQL은 엔티티 객체를 대상으로 쿼리함
- SQL은 DB 테이블을 대상으로 쿼리
JPQL 사용 이유
- JPA는 객체 중심적으로 코드를 작성함
- 검색(select)에서 한계에 부딪힘
- 모든 DB 테이블을 객체로 변환하여 검색해야함
- SELECT 쿼리를 수행할 때 함께 쓰이는 조건문을 작성하는 것에 한계를 보임
JPQL 문제점
- JPQL은 문자열(=String) 형태이기 때문에 개발자 의존적 형태
- 동적으로 쿼리 언어를 작성하는 데 효율적이지 못함
- Compile 단계에서 Type-Check가 불가능
- RunTime 단계에서 오류 발견 가능 (장애 risk 상승)
JPQL 문법
JPQL 문법은 SQL 문법과 매우 유사함
특징
- 엔티티와 속성은 대소문자를 구분한다. (Member(엔티티), age(속성))
- @Entity의 name을 지정하지 않으면, 클래스 이름이 엔티티 이름임
- JPQL 키워드는 대소문자를 구분하지 않음 (Select, FROM, where)
- 테이블 이름이 아닌 엔티티 이름을 사용함(Member, Team)
- 별칭(m) 사용은 필수적
- as는 생략이 가능함
- GROUP BY, HAVING, ORDER BY 등 모두 사용이 가능
TypeQuery, Query
- JPQL을 실행하려면 쿼리 객체를 만들어야 함
- 쿼리 객체로는 TypeQuery와 Query 존재
- 반환할 타입을 명확하게 지정할 수 있으면 TypeQuery 객체
- 명확하게 지정할 수 없으면 Query 객체를 사용
- TypeQuery 사용
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class); List<Member> resultList = query.getResultList(); for (Member member resultList) { System.out.println("member : " + member); }
Query 사용
Query query = em.createQuery("select m.username, m.age from Member m"); List resultList = query.getResultList(); for (Object o : resultList) { Object[] result = (Object[]) o; // 결과가 둘 이상이면 Object[] 반환 System.out.println("username : " + result[0]); System.out.println("age : " + result[1]); }
- Query 객체는 SELECT 문의 조회 대상이 둘 이상이면 Object[]를 반환
- 타입을 변환할 필요가 없는 TypeQuery를 사용하는 것이 더 편리함
파라미터 바인딩
- 이름 기준 파라미터와 위치 기준 파라미터 존재
- 위치 기준 파라미터보단 이름 기준 파라미터가 더 명확함
1. 이름 기준 파라미터
- 이름 기준 파라미터는 파라미터를 이름으로 구분하는 방법
String param = "leveloper"; TypedQuery<Member> query = em.createQuery("select m from Member m where m.username = :username", Member.class); query.setParameter("username", param); List<Member> resultList = query.getResultList();
2. 위치 기준 파라미터
- 위치 기준 파라미터를 사용하려면 ? 다음에 위치 값을 주어줌
- 위치 값은 1부터 시작
String param = "leveloper"; List<Member> members = em.createQuery("select m from Member m where m.username = ?1", Member.class) .setParameter(1, param) .getResultList();
프로젝션
- SELECT 절에 조회할 대상을 지정하는 것을 프로젝션
- 프로젝션 대상에는 엔티티, 임베디드 타입, 스칼라 타입
1. 엔티티 프로젝션
- 원하는 객체를 바로 조회하는 것이 컬럼을 하나하나 나열해서 조회해야 하는 SQL과는 차이가 있음
- 조회한 엔티티는 영속성 컨텍스트에서 관리됨
String query = "SELECT m FROM Member m"; List<Member> memberList = em.createQuery(query, Member.class) .getResultList();
2. 임베디드 타입 프로젝션
- JPQL에서 임베디드 타입은 엔티티와 거의 비슷하게 사용
- 임베디드 타입은 조회의 시작점이 될 수 없다는 제약이 있음
- 임베디드 타입은 엔티티 타입이 아닌 값 타입임
- 직접 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않음
String query = "SELECT o.address FROM Order o"; List<Address> addressList = em.createQuery(query, Address.class) .getResultList();
3. 스칼라 타입 프로젝션
- 숫자, 문자, 날짜와 같은 기본 데이터 타입들을 스칼라 타입
- 통계 쿼리도 주로 스칼라 타입으로 조회함
List<String> usernameList = em.createQuery("select username from Member m", String.class) .getResultList();
4. new 명령어
- 꼭 필요한 데이터들만 선택해서 조회해야 할 때도 있음
- 이럴 때는 UserDTO처럼 의미 있는 객체로 변환해서 사용
public class UserDTO{ private String username; private int age; public UserDTO(String username, int age){ this.username = username; this.age = age; } } TypedQuery<UserDTO> query = em.createQuery("select new jpabook.jpql.UserDTO(m.username, m.age) from Member m", UserDTO.class); List<UserDTO> resultList = query.getResultList();
페이징 API
JPA는 페이징을 다음 두 API로 추상화함
- setFirstResult (int startPosition) : 조회 시작 위치 (0부터 시작)
- setMaxResults (int maxResult) : 조회할 데이터 수
TypedQuery<Member> query = em.createQuery("select m from Member m order by m.username DESC", Member.class); query.setFirstResult(10); query.setMaxResults(20); List<Member> resultList = query.getResultList();
- 데이터베이스마다 다른 페이징 처리를 같은 API로 처리할 수 있는 것은 데이터베이스 언어 덕분
- 데이터베이스마다 SQL이 다른 것은 물론이고 오라클과 SQLServer는 페이징 쿼리를 따로 공부해야 SQL을 작성할 수 있을 정도로 복잡함
- 페이징 SQL을 더 최적화하고 싶다면 JPA가 제공하는 페이징 API가 아닌 네이티브 SQL을 직접 사용해야 함
JPQL 조인
- SQL 조인과 기능은 같고 문법만 약간 다름
내부 조인
- JPQL 조인의 가장 큰 특징은 연관 필드를 사용함
- 연관 필드는 다른 엔티티와 연관관계를 가지기 위해 사용하는 필드를 뜻함
String teamName = "teamA"; String query = "select m from Member m inner join m.team t where t.name = :teamName"; List<Member> memberList = em.createQuery(query, Member.class) .setParameter("teamName", teamName) .getResultList();
외부 조인
- 외부 조인은 기능상 SQL의 외부 조인과 같음
- OUTER는 생략 가능
SELECT m FROM Member LEFT (OUTER) JOIN m.team t
세타 조인
- 세타 조인은 전혀 관계없는 엔티티도 조인할 수 있음
- WHERE 절을 사용해서 세타 조인 가능
SELECT count(m) FROM Member m, Team t where m.username = t.name
페치 조인 (Fetch Join)
- 페치 조인은 SQL 조인의 종류가 아니고 JPQL에서 성능 최적화를 위해 제공하는 기능
- 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능으로 join fetch 명령어로 사용할 수 있음
- 페치 조인은 N + 1 문제를 해결하는 데 주로 사용되는 방법
String query = "select m from Member m join fetch m.team"; List<Member> memberList = em.createQuery(query, Member.class).getResultList(); for (Member member : memberList) { System.out.println("username : " + member.getUsername()); System.out.println("teamname : " + member.getTeam().getName()); }
회원과 팀을 지연 로딩(LAZY)로 설정했다고 하면, 회원을 조회할 때 페치 조인을 사용해서 팀도 함께 조회했으므로 연관된 팀 엔티티는 프록시가 아닌 실제 엔티티이게 됨
프록시가 아닌 실제 엔티티이므로 회원 엔티티가 영속성 컨텍스트에서 분리되어 준영속 상태가 되어도 연관된 팀을 조회할 수 있음'프로그래밍 기초 공부' 카테고리의 다른 글
Docker 란? (0) 2023.04.01 Kafka란? (0) 2023.03.31 DAO, DTO, VO란? (0) 2023.03.23 Spring Annotation 정리 (0) 2023.03.13 Stream (0) 2023.02.02