JPQL이란 객체지향 쿼리 언어로, JPA의 일부로 제공되며 데이터베이스 상호 작용에 사용된다. SQL은 테이블과 칼럼을 참조하나, JPQL은 엔티티와 필드를 대상으로 동작하는 쿼리이다.
엔티티를 대상으로 쿼리를 작성한다는 측면에서 객체지향인 맥락에 좀 더 부합하며, 데이터베이스와 관련된 세부 사항에 의존적이지 않다. SQL과 비슷한 문법을 가지며 JPQL도 결국 SQL로 변환된다. JPA에서 제공하는 메서드 호출 기능만으로 섬세한 쿼리 작성이 어렵다는 한계를 극복하기 위해
고안되었다.
String query = "select m from Member m where m.name = 'minjae'";
SQL과 문법이 유사하지만 다음과 같은 차이점이 존재한다.
엔티티와 속성은 대소문자를 구분한다. 엔티티 이름은 Member , 그리고 Member 의 속성 name 은
대소문자를 구분해주어야 한다. 반면에 SELECT, FROM, AS 같은 키워드는 대소문자를 구분하지
않아도 된다.
JPQL에서 사용한 Member 는 테이블 이름이 아니라 엔티티 이름이다. 엔티티 이름은 @Entity(name="minjae") 와 같은 형태로 설정할 수 있다. name 속성을 생략하면 기본값으로 클래스 이름을 사용한다.
JPQL에서 엔티티의 별칭은 필수적으로 명시해야 한다. 다만, 별칭을 명시하는 AS 키워드는 생략할 수 있다.
JPQL을 실행하려면 쿼리 객체를 만들어야 한다. TypedQuery와 Query가 있는데, 반환할 타입을 명확하게 지정할 수 있으면 TypedQuery , 명확하게 지정할 수 없으면 Query 객체를 사용한다.
String sql = "select m from Member m";
TypedQuery<Member> query = em.createQuery(jpql, Member.class);
List<Member> list = query.getResultList();
for(Member member : list) {
System.out.println("Member : "+member);
}
EntityManager 객체의 createQuery() 메서드를 통해 쿼리를 생성할 수 있다. TypedQuery는 호출 시 두번째 인자로 엔티티 클래스를 넘겨준다.
String sql = "select m.name, m.age from Member m";
Query query = em.createQuery(sql);
List<Object> list = query.getResultList();
for(Object obj : list) {
Object[] results = (Object[]) object;
for(Object result : results) {
System.out.print(result);
}
System.out.println();
}
Query 는 데이터 검색 결과의 타입을 명시하지 않는다.
query.getResultList() : 결과가 하나이상일 때, 리스트 변환query.getSingleResult() : 결과가 정확히 하나일 때, 단일 객체 반환javax.persistence.NoResultExceptionjavax.persistence.NonUniqueResultException파라미터 바인딩에는 이름 기준 파라미터와 위치 기준 파라미터가 있다. 더 명확하기 때문에 위치 기준 파라미터보다 이름 기준 파라미터를 권장한다.
String param = "minjae";
String sql = "select m from Member m where m.name = :name";
TypedQuery<Member> query = em.createQuery(sql, Member.class);
query.setParameter("name", param);
List<Member> list = query.getResultList();
이름을 기준으로 파라미터 바인딩을 수행한다. 콜론(: )을 사용해 데이터를 바인딩할 곳을 지정하고, query.setParameter 메서드를 호출해 데이터를 동적으로 바인딩 한다.
String param = "minjae";
String sql = "select m from Member m where m.name = ?1";
List<Member> members = em.createQuery(sql, Member.class)
.setParameter(1, param)
.getResultList();
위치 기준 파라미터를 사용하면 ? 다음에 위치 값을 주는 형식을 이용해 바인딩을 수행한다. 위치 값은 1부터 시작한다. 위와 같이 메서드 체이닝 방식으로 작성할 수도 있다.
엔티티 전체 데이터가 아닌 일부 필요한 데이터만을 추출하여 조회해야 할 수 있다. 이럴때 의미 있는 MemberDTO 와 같은 DTO 객체를 통해 JPQL을 작성할 수 있다.
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class MemberDTO {
private String email;
private String password;
private String name;
private int age;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public MemberDTO(String name, int age) {
this.name = name;
this.age = age;
}
}
String sql = "select new com.spring.example.dto.MemberDTO(m.name, m.age) from Member m";
TypedQuery<MemberDTO> query = em.createQuery(sql, MemberDTO.class);
List<MemberDTO> list = query.getResultList();
for(MemberDTO dto : list){
System.out.println("dto : "+dto);
}
DTO를 이용하여 조회할 떄는 다음과 같은 유의점이 존재한다.
com.spring.example.dto.MemberDTOMemberDTO(name, age)AS 키워드를 통해 명시적으로 매핑할 수 있다.new 는 JPQL에서 제공하는 키워드로 쿼리 결과를 엔티티 대신 사용자가 지정한 DTO나 다른 객체로 매핑하는 데 활용된다.이런 방식의 쿼리를 통해 DB에서 불필요한 데이터를 배제하고 필요한 정보만을 조회하여 성능을 개선할 수 있다.