Hyeonuk_.log

자바 ORM 표준 JPA 프로그래밍 읽다가 [10장 객체지향 쿼리 언어-1] 본문

Dev_.log/Spring

자바 ORM 표준 JPA 프로그래밍 읽다가 [10장 객체지향 쿼리 언어-1]

Hyeonuk_. 2022. 5. 20. 05:49

JPQL (Java Persistence Query Language)

특징


- 객체지향 쿼리 언어다. 따라서 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
- SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
- 결국 SQL로 변환된다.

SELECT 문


SELECT m FROM Member AS m where m.username = 'Hello'
엔티티와 속성은 대소문자 구분한다, Member는 클래스 명이 아니라 엔티티명이다. 별칭은 필수이다.

TypeQuery, Query


작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 한다. 쿼리 객체는 TypeQuery와 Query가 있는데 반환할 타입을 명확하게 지정할 수 있으면 TypeQuery 객체를 사용하고, 반환 타입을 명확하게 지정할 수 없으면 Query 객체를 사용하면 된다.

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 = em.createQuery("SELECT m.username, m.age FROM Member m");
// 반환할 타입이 명확하지 않는 경우, username은 String age는 Integer

List resultList = query.getResultList();
for(Object o : resultList) {
	Object[] result = (Object[]) o; // 결과가 둘 이상이면 Object[] 반환, 하나면 Object
    System.out.println("username = " + result[0]);
    System.out.println("age = " + result[1]);
}

query.getResultList() 를 사용할 때 만약 결과가 없으면 빈 컬렛견을 반환한다.

 

파라미터 바인딩

String usernameParam = "User1";

// 이름 기준 파라미터
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.username = :username", Member.class);
query.setParameter("username", usernameParam);

List<Member> resultList = query.getResultList();

// 위치 기준 파라미터
TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m where m.username 
= ?1", Member.class).setParameter(1, usernameParam);

List<Member> resultList = query.getResultList();

 

프로젝션


SELECT절에서  조회할 대상을 지정하는 것을 프로젝션이라 하고 [SELECT (프로젝션 대상) FROM]으로 대상을 선택한다.
프로젝션 대상은 엔티티, 임베디드 타입, 스칼라 타입이 있다.

엔티티 프로젝션

SELECT m FROM Member m
SELECT m.team FROM Member m

// 조회한 엔티티는 영속성 컨텍스트에서 관리된다.

 

임베디드 타입 프로젝션
임베디드 타입은 조회 시작점이 될 수 없다는 제약이 있다. 임베디드 타입은 엔티티 타입이 아닌 값 타입이다. 따라서 직접 조회한 임베디드 타입은 영속성 컨텍스트에서 관리되지 않는다.

String query = "SELECT o.address FROM Order o"; // Order를 통해서 Address 접근
List<Address> addresses = em.createQuery(query, Address.class).getResultList();
select
	order.city,
	order.street,
	order.zipcode
from
	Orders order

 

스칼라 타입 프로젝션
숫자, 문자, 날짜와 같은 기본 데이터 타입들을 스칼라 타입이라 한다. 예를 들어 전체 회원의 이름을 조회하려면 다음처럼 쿼리하면 된다.

List<String> username = em.createQuery("SELECT username FROM Member m", String.class)
							.getResultList();

Double orderAmountAvg = em.createQuery("SELECT AVG(o.orderAmount) FROM ORder o", Double.class)
							.getResultList();

여러값 조회

Query query = em.createQuery("SELECT m.username, m.age FROM Member m");
List resultList = query.getResultList();

Iterator iterator = resultList.iterator();
while(iterator.hasNext()) {
	Object[] row = (Object[]) iterator.next();
    String username = (String) row[0];
    Integer age = (Integer) row[2];
}

Object[]로 좀 더 간결하게

List<Object[]> resultList = em.createQuery("SELECT m.username, m.age FROM Member m").getResultList();

for(Object[] row : resultList) {
	String username = (String) row[0];
    Integer age = (Integer) row[1];
}

스칼라 값 뿐만 아니라 엔티티로 조회

List<Object[]> resultList = 
		em.createQuery("SELECT o.member, o.product, o.orderAmount FROM Order o").getResultList();

for(Object[] row : resultList) {
	Member member = (Member) row[0];
	Product product = (Product) row[1];
	int orderAmount = (Integer) row[2];
}

New 명령어 사용

List<Object> resultList =
		em.createQuery("SELECT m.username, m.age FROM Member m").getResultList();

//객체변환 작업
List<UserDTO> userDTOs = new ArrayList<UserDTO>();
for(Object[] row : resultList) {
    UserDTO userDTO = new UserDTO((String) row[0], (Integer)row[1]);
    userDTOs.add(userDTO);
}
return userDTOs;



// NEW 명령어 사용하면
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

페이징 처리용 SQL을 작성하는 일은 지루하고 반복적이다. 더 큰 문제는 데이터베이스마다 페이징을 처리하는 SQL 문법이 다르다는 점이다. JPA는 페이징을 다음 두 API로 추상화했다.

- setFirstResult(int startPosition) : 조회 시작 위치(0부터 시작한다)
- setMaxResult(int maxResult) : 조회할 데이터 수

TypedQuery<Member> query = 
            em.createQuery("SELECT m FROM Member m ORDER BY m.username DESC",Member.class)
            .setFirstResult(10)
            .setMaxResult(20);
            // 11번째부터 시작해서 총 20건의 데이터를 조회
            
query.getResultList();

 

 

Comments