JPQL도 SQL과 비슷하게 SELECT, UPDATE, DELETE 문을 사용할 수 있다. 저장할 때에는 엔티티 매니저를 사용하기 때문에 INSERT문은 없다.
SELECT :: =
SELECT_절
FROM_절
[WHERE_절]
[GROUPBY_절]
[HAVING_절]
[ORDERBY_절]
UPDATE :: = UPDATE_절 WHERE_절
DELETE :: = DELETE_절 WHERE_절
SELECT 문
EX) SELECT m FROM Member AS m where m.username = 'hi'
- 엔티티의 속성은 대소문자를 구분한다. 하지만 JPQL의 키워드는 대소문자를 구분하지 않는다.
- JPQL에서 사용한 Member는 클래스명이 아니라 엔티티명이다. @Entity(name="Member")로 지정된 엔티티이며,엔티티명을 지정하지 않은 경우에는 클래스며을 기본 값으로 사용한다.
- JPQL에서 AS를 활용한 별칭은 필수로 사용해야한다.
작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 한다. 쿼리 객체는 TypeQuery와 Query가 있는데 반환할 타입을 명확하게 지정할 수 있다면 TypeQuery 객체를 사용하고, 타입이 불확실한 경우에는 Query객체를 사용한다.
em.createQuery() 메서드의 두 번째 인자에 반환할 타입을 지정하면 TypeQuery객체를 그렇지 않으면 Query객체를 반환한다.
TypedQuery<Member> query = em.createQuery("select m from Member m",Member.class);
List<Member> resultList = query.getResultList();
만약 조회 대상의 String타입과 Integer타입 두 개를 반환한다면 Query객체를 사용해야 하며, Object [] 타입을 반환한다.
무슨 말인가 하면 SQL을 사용할 경우는 조회한 속성 값을 포함한 Member객체를 반환했다면 JPQL은 해당 조회한 속성만 반환한다.
Query 사용 예제를 보자.
Query query =
em.createQuery("SELECT m.suername, m.age FROM Mebmer m");
List resultList = query.getResultList();
for (Object o : resultList){
Object[] result = (Object[]) o;
System.out.printIn("username = " + result[0]);
System.out.printIn("age = " + result[1] );
}
결과 조회를 위한 메서드들이며, 호출 시 실제 쿼리를 실행해 데이터베이스를 조회한다.
- query.getResultList() //결과가 없을 시 빈 컬렉션을 반환한다.
- query.getSingleResult() //결과가 한 개가 아닐 시 예외가 발생한다.
JPQL은 파라미터 바인딩을 이름 기준으로도 지원한다.
String usernameParam = "User";
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.username = :username",
Member.class);
query.setParameter("username", usernameParam);
혹은 메서드 체인 방식으로 작성할 수도 있다.
List<Member> members = em.createQuery("SELECT m FROM Member m where m.username = :username",
Member.class)
.setParameter("username", usernameParam)
.getResultList();
또한 JDBC처럼 위치 기반으로 1, 2, 3으로 파라미터를 바인딩할 수 있는데 더 명확한 건 이름 기반으로 파라미터를 바인딩하는 것이 더 좋다.
만약 파라미터 바인딩을 사용하지 않고 단순히 문자를 더하기로 만들어 넣게 된다면 SQL 인젝션 공격을 당할 수 있다.
프로젝션
SELECT 절에 조회할 대상을 지정하는 것을 projection이라 한다. 프로젝션 대상은 엔티티, 엠비 디드 타입, 스칼라 타입이 있다.
엔티티
- SELECT m FROM Member m
임베디드 타입 프로젝션
- 임베디드 타입은 엔티티의 속해있는 값 타입이다. 그렇기에 절대 시작점이 될 수 없으며 영속성 콘텍스트에서 관리되지 않는다.
- SELECT o.address FROM Order o;
스칼라 타입 프로젝션
- List <> String usernames = em.createQuery("SELCT username FROM Member m" , String.class). getResultList();
- 중복 데이터를 제거하려면 DISTINCT키워드 사용한다.
여전히 프로젝션 대상이 여러 개라면 Query객체를 사용해 여러 타입을 받을 수밖에 없다. 다만 실제로는 UserDTO처럼 의미 있는 객체로 변환해서 사용할 것이다.
package chap13;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Main {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("spring5");
EntityManager em = emf.createEntityManager();
List<Object[]> resultList =
em.createQuery("SELECT m.username, m. age from Member m")
.getResultList();
List<UserDTO> userDTOs = new ArrayList<>();
for(Object[] o : resultList) {
UserDTO userDTO =new UserDTO((String)o[0],(int)o[1]);
userDTOs.add(userDTO);
}
}
}
편하게 NEW명령어를 사용해 객체 변환 작업을 없애보자
TypedQuery<UserDTO> query = em.createQuery("SELECT new chap13.UserDTO(m.username, m.age) FROM Member m"
,UserDTO.class);
List<UserDTO> resultList = query.getResultList();
select다음에 new명령어를 사용하면 반환받을 클래스를 지정할 수 있고 이 클래스 생성자에 JPQL조회 결과를 넘겨줄 수 있다. 대신 패키지 명을 포함한 전체 클래스 명을 입력해야 한다. 또한 순서와 타입이 일치하는 생성자가 필요하다.
'데이터 접근 기술 > JPA' 카테고리의 다른 글
[JPA] 벌크 연산 (0) | 2021.01.31 |
---|---|
[JPA] JPQL JOIN (0) | 2021.01.31 |
[JPA] JPQL 소개 (0) | 2021.01.30 |
[JPA] 즉시 로딩과 지연 로딩 (0) | 2021.01.28 |
[JPA] 상속 관계 매핑 (0) | 2021.01.27 |
댓글