ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA > JPQL, 프로젝션
    BackEnd/JPA 2021. 4. 27. 19:11

    JPA 를 사용할 때 EntityManager.find() 를 사용하면 엔티티 하나를 조회할 수 있다. 하지만 특정 조건이 있는 모든 객체를 검색하고자 할 때는 보다 복잡한 검색 방법이 필요하다.

    JPQL

    테이블이 아닌 엔티티 객체를 대상으로 검색하는 객체지향 쿼리 
    SQL 을 추상화해서 특정 벤더에 의존적이지 않다.
    JPA 는 JPQL 을 분석해 SQL 을 생성하고 실행한다.

     

    JPQL 은 아래와 같은 형태로 작성된다.

    select m from Member as m where m.age > 18 

    1. 대소문자 구분 

    Member 와 age 같은 엔티티와 속성은 대소문자를 구분한다.

     

    2. 엔티티 이름 

    Member 는 클래스 명이 아니라 엔티티 명이다. 엔티티명은 @Entity(name="") 으로 지정할 수 있다. 엔티티 명을 지정하지 않으면 클래스명이 기본값이다.

     

    3. 별칭은 필수

    as "m" 과 같은 별칭은 필수적이며 as 는 생략 가능하다.

    m.age 에서 age 는 반드시 엔티티에 있는 변수명으로 작성해야한다.

     

    JPQL 을 실행하려면 쿼리 객체를 만들어야한다. 쿼리객체에는 TypeQuery 와 Query 가 있다.

    TypeQueryQuery 

    TypeQuery 는 반환 타입이 명확할 때,  

    Query 는 반환타입이 명확하지 않을 때 사용한다.

     

    예를 들어 String 타입만 반환할 때는 TypeQuery 를

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

    여러 엔티티와 컬럼을 선택해야 할 때, 아래 예제처럼 String 와 Int 변수를 함께 반환할 때는 Query를 사용한다.

    Query 객체는 여러 타입을 반환할 때는 Object[] 를 , 하나의 타입일 때는 Object 를 반환한다. Object 는 타입 변환이 필요하므로, 하나만 있을 때는 TypeQuery 를 쓰는 것이 낫다.

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

     

    결과 조회를 하기 위해서는 아래 메서드를 호출한다.

    query.getResultList() : 결과가 없으면 빈 컬렉션을 반환한다.

    query.getSingleResult(); 결과가 하나일 때 사용한다.결과가 없으면 NoResultException 이 발생하고, 1개보다 많으면 NonUniqueResultException 이 발생한다. 

     

    파라미터 바인딩 

    JDBC 는 위치 기준 파라미터 바인딩만 지원하지만, JPQL 은 이름 기준 파라미터 바인딩도 지원한다.

     

    1. 이름기반 파라미터

    이름 기반 파라미터는 파라미터 값이 달라도 같은 쿼리로 인식되어 SQL 이 재사용된다.

    String username = "aa";
    
    TypedQuery<Member> q = 
        em.createQuery("select m from Member m where m.username= :username", Member.class);
    
    q.setParameter("username",username);
    
    List<Member> result = q.getResultList();

    2. 위치기반 파라미터

    위치기반 파라미터 사용은 SQL Injection 공격에 취약하고, SQL 재사용이 되지 않아 이름 기반보다 성능이 떨어진다.

    String username = "aa";
    
    List<Member> result = 
        em.createQuery("select m from Member m where m.username= ?1", Member.class);
          .setParameter("username",username);
          .getResultList();

    프로젝션

    프로젝션은 SELECT 절에 조회할 대상을 지정하는 것을 말한다.

    엔티티 프로젝션, 임베디드 타입 프로젝션, 스칼라 타입 프로젝션이 있다. 스칼라는 숫자, 문자 등 기본 타입을 말한다.

    SELECT m FROM Member m // Member 에서 멤버조회 > 엔티티 프로젝션
    SELECT m.team FROM Member m //Member 에서 Team(Fk) 조회 > 엔티티 프로젝션
    SELECT m.address FROM Member m //Member 에서 Address 값 타입 객체 조회 > 임베디드 타입 프로젝션
    SELECT m.username, m.age FROM Member m //Member 의 primitive 값 타입 조회 > 스칼라 타입 프로젝션

    위에서 Member 객체에서 Team 객체 정보를 가져오기 위한 두 번째 쿼리에서는 자동으로 조인문이 생성된다. (묵시적)

    하지만 가독성, 유지보수를 용이하게 하기 위해서 SQL 문과 유사하게 쿼리를 작성하는 것이 좋다. (명시적)

     SELECT m.team FROM Member m 
     => SELECT t from Member m  join m.team t

    1. 엔티티 프로젝션 

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

    2. 임베디드 프로젝션 

    임베디드 타입은 조회의 시작점이 될 수 없다는 제약이 있다.

    예를 들어 "SELECT a FROM Address a"; 은 잘못된 쿼리이다. 

    "SELECT o.address FROM Order  o" 처럼 사용해야한다.

     

    3. 여러값 조회와 new 명령어 

    프로젝션으로 여러값을 조회하는 방법에는 세 가지가 있다.

    여러값을 조회하기 위해서는 TypeQuery 는 사용 불가하고 Query 타입만 사용할 수 있다.

    Query 타입은 Object[]를 반환해 아래처럼 사용할 수 있지만 실무에서는 DTO 를 생성해 사용하는 방법을 사용한다.

     

    1. Object[] 배열 조회 

    List resultList = em.createQuery("select m.userName, m.age from Member m").getResultList();
    
    Object o = resultList.get(0);
    Object[] result = (Object[]) o;
    System.out.println("result[0] = " + result[0]);
    System.out.println("result[0] = " + result[1]);

     

    2. 조회용 DTO 를 생성

    new DTO 를 select 할 때 DTO의 패키지명을 포함한 전체 클래스명을 입력해야하며, 

    생성자의 매개변수와 쿼리 안에 변수명 순서를 동일하게 맞춰야한다.

    패키지명이 길어지면 작성하고 읽는데 불편함이 오는데 QueryDSL 을 사용하면 이런 불편함도 극복할 수 있다.

    List<MemberDTO> resultList 
            = em.createQuery("select new jpql.MemberDTO(m.userName, m.age) from Member m", MemberDTO.class)
             .getResultList();
    public class MemberDTO {
        private String userName;
        private int age;
        public MemberDTO(String username, int age){//..}
    //..
    }

     

    페이징 API

    페이징 처리용 SQL 관련된 메서드는 아래와 같다.

    setFirstResult(10) : 조회 시작 위치를 명시한다. SQL 의 offset 

    setMaxResult(10); 조회할 데이터 수를 명시한다. SQL 의 limit

     

    JPQL 조인 

    JPQL 조인의 특징은 연관 필드를 사용한다는 것이다. 연관필드는 다른 엔티티와 연관관계를 가지기 위해 사용하는 필드를 말한다.

    List resultList = 
        em.createQuery("select m from Member m INNER JOIN m.team t 
                        + "WHERE t.name = :teamName");                 
                       .getResultList();

    위 코드는 아래와 같이 SQL 문에서 자동으로 Memeber 의 TeamID 와 Team 의 ID 를 조인 조건으로 만들어준다.

    SELECT.. FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID=T.ID WHERE T.NAME=?

    잘못된 사용 -SQL 처럼 작성하면 문법오류가 난다.

    from Member m JOIN Team t

     

    세타 조인 

    where 절을 사용해 전혀 관계없는 엔티티를 조회할 수 있다.  Inner Join 만 지원한다.

    아래 Member 의 username 과 Team 의 name 은 서로 관계가 없는 필드인데 조인해서 사용하고자 할 때 세타 조인을 할 수 있다.

    이 경우 SQL 의 CROSS JOIN 이 발생한다.

    //JPQL
    select count(m) from Member m, Team t where m.username=t.name;
    
    //SQL
    select count(m.id) from Member m cross join Team t where m.username=t.name;

     

    JOIN ON 절 (JPA 2.1)

    On 은 조인 대상을 필터링하고 조인한다. 내부 조인의 on 은 where 와 결과가 같아 보통 외부조인에서만 사용한다.

     

    //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 을 name 으로 필터링 한 뒤 join 한다.

     


     

    www.inflearn.com/course/ORM-JPA-Basic/dashboard

     

    자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

    JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔

    www.inflearn.com

     

    반응형

    'BackEnd > JPA' 카테고리의 다른 글

    JPA > Fetch Join , BatchSize  (0) 2021.04.27
    JPA 경로표현식  (0) 2021.04.27
    JPA > 값 타입  (0) 2021.04.25
    JPA > 영속성 전이와 고아객체  (0) 2021.04.25
    JPA > 연관관계 매핑  (0) 2021.04.18
Designed by Tistory.