JPQL(Java Persistence Query Language)

Minjae An·2023년 12월 17일
0

Spring data JPA

목록 보기
2/5

JPQL(Java Persistence Query Language)

JPQL이란 객체지향 쿼리 언어로, JPA의 일부로 제공되며 데이터베이스 상호 작용에 사용된다. SQL은 테이블과 칼럼을 참조하나, JPQL은 엔티티와 필드를 대상으로 동작하는 쿼리이다.

엔티티를 대상으로 쿼리를 작성한다는 측면에서 객체지향인 맥락에 좀 더 부합하며, 데이터베이스와 관련된 세부 사항에 의존적이지 않다. SQL과 비슷한 문법을 가지며 JPQL도 결국 SQL로 변환된다. JPA에서 제공하는 메서드 호출 기능만으로 섬세한 쿼리 작성이 어렵다는 한계를 극복하기 위해
고안되었다.

JPQL의 특징

  • 테이블이 아닌 객체를 검색하는 객체지향 쿼리
  • SQL을 추상화 했기 때문에 특정 벤더에 종속적이지 않다
  • JPA는 JPQL을 분석하여 SQL을 생성한 후 DB에서 조회

기본 문법

String query = "select m from Member m where m.name = 'minjae'";

SQL과 문법이 유사하지만 다음과 같은 차이점이 존재한다.

대소문자 구분

엔티티와 속성은 대소문자를 구분한다. 엔티티 이름은 Member , 그리고 Member 의 속성 name
대소문자를 구분해주어야 한다. 반면에 SELECT, FROM, AS 같은 키워드는 대소문자를 구분하지
않아도 된다.

엔티티 이름

JPQL에서 사용한 Member 는 테이블 이름이 아니라 엔티티 이름이다. 엔티티 이름은 @Entity(name="minjae") 와 같은 형태로 설정할 수 있다. name 속성을 생략하면 기본값으로 클래스 이름을 사용한다.

별칭(alias)

JPQL에서 엔티티의 별칭은 필수적으로 명시해야 한다. 다만, 별칭을 명시하는 AS 키워드는 생략할 수 있다.

Typed Query, Query

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.NoResultException
    • 둘 이상이면 : javax.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부터 시작한다. 위와 같이 메서드 체이닝 방식으로 작성할 수도 있다.

DTO 이용 조회

엔티티 전체 데이터가 아닌 일부 필요한 데이터만을 추출하여 조회해야 할 수 있다. 이럴때 의미 있는 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.MemberDTO
  • 순서와 타입이 일치하는 생성자가 필요하다. MemberDTO(name, age)
    다만 엔티티 필드명과 DTO의 생성자 파라미터명이 일치하지 않더라도 AS 키워드를 통해 명시적으로 매핑할 수 있다.
  • new 는 JPQL에서 제공하는 키워드로 쿼리 결과를 엔티티 대신 사용자가 지정한 DTO나 다른 객체로 매핑하는 데 활용된다.

이런 방식의 쿼리를 통해 DB에서 불필요한 데이터를 배제하고 필요한 정보만을 조회하여 성능을 개선할 수 있다.

profile
도전을 성과로

0개의 댓글