[JPA] JPQL 기본문법(JPA 기본편 by 김영한)

su_y2on·2022년 2월 7일
1

JPA

목록 보기
14/17
post-thumbnail

JPQL 기본문법

이제까지 단순히 특정 pk값의 객체를 조회할 때 em.find()를 사용해왔습니다. 하지만 만약 조건이 더 복잡한 조회를 해야한다면 JPQL을 사용해야합니다! JPQL은 객체지향적으로 쿼리문을 작성할 수 있게 도와는 JPA가 제공하는 객체 지향 쿼리 언어입니다. 이렇게 작성된 JPQL은 SQL로 변경되어 쿼리가 날라갑니다.




JPQL의 문법은 SQL과 비슷합니다. 다만 엔티티 객체를 대상으로 한다는 것이 다릅니다. JPQL은 특정 데이터베이스의 SQL에 의존하지않습니다.

아래는 19세 이상인 Member를 조회하는 JPQL입니다.

String jpql = "select m from Member m where m.age > 18";List<Member> result = em.createQuery(jpql, Member.class).getResultList();

아래는 SQL로 변환된 쿼리입니다.

select
	m.id as id,
	m.age as age,
	m.USERNAME as USERNAME,
	m.TEAM_ID as TEAM_ID
from
	Member m
where
    m.age>18




JPQL 기본

  1. JPQL은 엔티티와 속성에 대해서는 대소문자를 구분합니다. 따라서 Member -> member로 사용하는것은 안됩니다. 또한 age -> Age로 사용하는 것도 오류입니다!

  2. JPQL 키워드는 대소문자를 구분하지 않습니다. SELECT, WHERE같은 것들이 이에 속합니다. 따라서 대소문자를 구분하지 않고 사용해도 인식합니다

  3. JPQL에 사용하는 이름들은 테이블이 아닌 엔티티 이름입니다.

  4. 별칭은 필수적으로 사용해야합니다. 이때 AS는 생략가능합니다.




TypeQuery vs Query

쿼리에는 두가지 타입이 있습니다. 첫번째는 TypeQuery로 조회해올 객체의 타입이 확실할 때 사용합니다. 두번째는 Query로 반환타입이 명확하지 않을 때 사용합니다.

지금과 같은 경우 명확히 Member객체가 반환되지만

TypedQuery<Member> query = em.createQuery("SELECT m FROM Member m", Member.class);

아래와 같이 특정 요소만 select할 경우 정확히 어떤 타입이라고하기 애매합니다.

Query query = em.createQuery("SELECT m.username, m.age from Member m");




결과 조회 API

이렇게 반환된 객체를 출력하기 위해서 결과 조회 함수를 사용해야합니다. 먼저 getResultList()함수는 결과가 하나 이상일 때 사용합니다. 결과가 없다면 빈 리스트를 반환합니다. getSingleResult()정확히 결과가 하나일 때만 사용해야합니다. 만약 결과가 없거나 둘이상이라면 각각 에러를 일으킵니다. 따라서 주의가 필요합니다.




파라미터 바인딩

쿼리문에는 파라미터를 사용할 수도 있습니다. 위에서의 문법을 모두 적용하면 아래와 같이 나타낼 수있습니다. 파라미터로 쓰고싶은 변수앞에 :를 붙여줍니다. 그리고 해당 변수에 값을 setParameter로 넣어줍니다.

 Member singleResult1 = em.createQuery("select m from Member as m where m.username = :username", Member.class)
                    .setParameter("username", "member1")
                    .getSingleResult();




이때 파라미터 바인딩은 아래와 같이 위치기반으로도 가능하지만 만약 파라미터가 여러개이고 중간에 수정이생긴다면 매우 번거로워지기때문에 위처럼 이름을 기준으로 하는 것이 바람직합니다.

em.createQuery("select m from Member as m where m.username = ?1", Member.class)
.setParameter(1, "member1");




프로젝션

프로젝션은 SELECT문에서 조회할 대상을 지정하는 것입니다. 프로젝션의 대상엔티티, 임베디드 타입, 스칼라타입(기본데이터타입)이 있습니다.


지금과 같이 Member를 통해서 team에 접근할 때는 내부적으로 join쿼리가 날라갑니다. 따라서 이럴때는 join이 보이도록 JPQL을 작성하는 것이 이후에 성능을 한눈에 파악하기 좋습니다.

// 엔티티 프로젝션
List<Team> findTeam = em.createQuery("select m.team from Member m", Team.class)
					.getResultList();

// join보이도록 쿼리짜기
            List<Team> findTeam1 = em.createQuery("select t from Member m join m.team t", Team.class)
                    .getResultList(); 
// 임베디드 프로젝션                  
List<Address> findAddress = em.createQuery("select o.address from Order o", Address.class)
                    .getResultList(); 
                                      

스칼라타입은 위와 다르게 여러가지 속성을 받을 수 있어서 결과를 받아오는 타입 선택이 곤란합니다. 여러 방법들이 있는데 첫 번째 방법은 Object배열의 리스트로 받아오는 것입니다. 첫번째 Member의 username과 age는 .get(0)으로 가져온뒤 에 인덱싱해서 출력할 수 있습니다.

// 스칼라타입 프로젝션  : Object[]                 
List<Object[]> resultList1 = em.createQuery("select m.username , m.age from Member m")
                    .getResultList(); 
                    
Object [] result = resultList1.get(0);
            System.out.println("username = " + result[0]);
            System.out.println("age = " + result[1]);    



// 스칼라타입 프로젝션  : DTO
List<MemberDTO> resultList2 = em.createQuery("select new jpql.MemberDTO(m.username , m.age) from Member m", MemberDTO.class)
                    .getResultList();



두 번째 방법은 DTO를 만들어 놓고 생성자를 이용해서 특정형식으로 값을 가져오는 것입니다. 아래처럼 DTO클래스를 하나 만들어 주고 받아올 형식을 맞춰서 생성자도 만들어줍니다. 이때 생성자의 파라미터 순서와 select문에서 넘겨주는 순서가 같아야합니다. 그리고 new MemberDTO가 아닌 패키지명을포함한 전체클래스명(jqpl.MemberDTO)으로 입력해야합니다.

MemberDTO

public class MemberDTO {

    private String username;
    private int age;

    public MemberDTO(String username, int age) {
        this.username = username;
        this.age = age;
    }
}



페이징

조회할 때 페이징을 써서 가져오고 싶다면 setFirstResult로 시작setMaxResults로 총 몇개를 가져올지 설정해주면 됩니다. 지금같이 하면 나이가 많은 순으로 10명을 가져옵니다.

List<Member> findMember = em.createQuery("select m from Member m order by m.age desc", Member.class)
                    		.setFirstResult(0)
                    		.setMaxResults(10)
                   		    .getResultList();



조인


내부조인은 아래와 같이 join이라고 쓰면 됩니다. 그리고 객체지향적이기 때문에 m.team t로 연관관계를 참조하는 형식으로 써줍니다

List<Member> resultList3 = em.createQuery("select m, t from Member m join m.team t", Member.class)
                    		.getResultList();

이에 대한 SQL쿼리는 Member와 Team이 조인을 했고 FK(team id)가 같은 애들만 뽑아냅니다

SELECT m.*, t.* 
FROM Member m 
INNER JOIN Team t 
ON m.TEAM_ID=t.id




외부조인left join으로 쓰면 됩니다.

List<Member> resultList4 = em.createQuery("select m, t from Member m left join m.team t", Member.class)
                   			 .getResultList();




만약 조인 대상을 더 필터링 하고 싶거나 연관관계가 없는 엔티티 외부조인을 하고싶다면 JPQL에서 ON문을 작성해주면됩니다

아래의 예제는 조인을 할때 팀의 이름은 A인 팀만 조인을 합니다.

// 조인 대상 필터링 : JPQL
SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'

// 조인 대상 필터링 : SQL 
SELECT m.*, t.* 
FROM Member m 
LEFT JOIN Team t 
ON m.TEAM_ID=t.id and t.name='A'

연관관계가 있는 Team_id값으로 조인을 하는 것이 아닌 username과 name이 같은 애들을 추출해야할때

// 연관관계없는 엔티티 외부조인 : JPQL
SELECT m, t FROM
 Member m LEFT JOIN Team t on m.username = t.name

// 연관관계없는 엔티티 외부조인 :SQL
SELECT m.*, t.* 
FROM Member m 
LEFT JOIN Team t 
ON m.username = t.name




서브쿼리

SQL과 마찬가지로 서브쿼리도 JPQL에서 지원됩니다. 하지만 FROM문에 대한 서브쿼리는 지원되지 않으며 조인으로 최대한 풀어야합니다.





요즘 SQL도 함께 복습을 하는중이라 그런지 JPQL과 비슷한 점이 정말 많다고 느껴집니다. 하지만 미묘한 차이들도 존재하니 유의해서 써야할 것 같네용🧐

0개의 댓글