ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [스프링] JPQL 정리
    스프링 2022. 12. 26. 11:48
    728x90

    JPQL이란?

    JPA에서 제공하는 메소드 만으로는 섬세한 SQL 쿼리 작성이 어렵다는 문제에서 탄생한, 엔티티 객체를 조회하는 객체지향 쿼리이다.

    또한, 테이블을 대상으로 쿼리하는 것이아니라 엔티티 객체를 대상으로 쿼리한다.

     

    JPQL 특징

    1. 객체지향 쿼리 언어이다. ( 테이블을 대상으로 쿼리 X, 객체 대상으로 쿼리 O )

    2. DB SQL에 의존하지 않는다

    3. SQL과 비슷한 문법을 가지며, 결국 JPQL은 SQL로 변환되어서 사용된다

     

    간단한 기본 문법 예시

    String jpql = "select m from Member as m where m.name = 'coco'";

     

    1. 기본 문법과 쿼리 API

    JPQL은 SELECT, 벌크연산( UPDATE, DELETE ) 사용가능, INSERT는 없읍 ( persist() 있기 떄문 )

    1) 쿼리 문법

    • 엔티티와 속성은 대소문자 구분
    • 키워드는 구분 x
    • 엔티티 이름 사용, 테이블 이름 아님.
    • 별칭은 필수 (여기서는 ex - m)
    // 엔티티 이름 설정 방법
    @Entity(name="") 
    // 하지만 name 안쓰면 기본값 사용되는데, 기본값 사용을 추천
    
    // 별칭 쓰는 방법
    select m from Member (as) m where sum.age > 18

     

    2) TypeQuery, Query

    1. TypeQuery : 반환 타입이 명확할 때 사용

    2. Query : 반환 타입이 명확하지 않을 때 사용

    // createQuery 두번째 파라미터에 반환할 타입 명시 
    // TypeQuery 이 더 편리
    TypeQuery<Member> query = em.createQuery(“select m from Member m”, Member.class) 
    TypeQuery<String> query = em.createQuery(“select m.username from Member m”,String.class)

     

    3) 집합과 정렬

    Select
    count(m) // 반환 타입 Long
    MAX, MIN
    avg(m), sum(m)
    ~
    Groub py, having 그대로 사용 가능
    집합함수 사용시 주의 사항
    1. NULL 값은 무시하므로 통계에 잡히지 않는다.
    2. 만약 값이 없는데 함수들을 사용하면 NULL값이 된다 ( COUNT 는 0이 된다 )
    3. DISTINCT를 COUNT 안에서 사용할 때 임베디드 타입은 지원하지 않는다

     

    통계 쿼리, 리포팅 쿼리 ( Groub py, having  )

    이러한 쿼리들은 실시간으로 사용하기에는 부담이 있다

    결과가 많다면 통계 결과만 저장하는 테이블을 별도로 만들고, 사용자가 적은 새벽에 통계 쿼리를 실행해서 결과를 보관하는 것이 좋다

     

    정렬 

    ORDER BY 사용
    
    // 예시 - 나이 기준 내림차순, 나이가 같으면 이름 기준 오름차순
    select m from Member m order by m.age DESC, m.username ASC
    DESC = 내림차순
    ASC = 오름차순

     

    2. 결과 조회 API

    2가지 있는데 첫번째 방법 사용하기

    // 1번쨰 방법
    List resultlist = query.getResultList();
    
    // 2번쨰 방법
    Member result = query.getSingleResult();

    첫번째 방법은 결과 없으면 빈 컬렉션 반환, 널 포인터 면역이 좀 있다

    두번째 방법은 결과각 정확히 1개일때만 예외 발생안함 ( 여러 논란 있음 )

     

    3. 파라미터 바인딩 - 이름기준, 위치기준 

    파라미터 바인딩은 성능을 이한 선택이 아닌 필수이다.

    String jqpl = “select m from Member m where m.username = :username”
    TypeQuery<Member> query = em.createQuery(jqpl, Member.class)
    query.setParameter(“username”,”member1”);
        .setParameter(“username,”member1”) 
        .getSingleResult() //체이닝
    // 위치기반 무시하고 이름 기반 사용하기

     

    4.  프로젝션 : select절에 조회할 대상을 지정하는 것. 

    1) 프로젝션

    프로젝션 대상 : 엔티티, 임베디드 타입, 스칼라타입 (숫자, 문자 등 기본 데이터 타입).

    참고로, RDBMS는 스칼라만 가능하다

    [ SELECT { 프로젝션 대상 } FROM ]
    
    select m from member m // 엔티티 프로젝션
    Select m.team from member m  // 엔티티 프로젝션
    Select m.address from member m // 임베디드 타입 프로젝션
    Select m.usernmae , m.age from member m // 스칼라타입 프로젝션
    //Distinct로 중복 제거

    엔티티 프로젝션 : 영속성 컨텍스트로 다 관리가 됨.

    List<Member> result = em.createQuery(“select m from member m”,Member.class).getResultList()
    • 여기서 result은 영속성 컨텍스트에 관리가 될까?
      -> 전부 다 영속성 컨텍스트에 관리가 됩니다.

    엔티티 프로젝션

    List<Team> result = em.createQuery(“select m.team from member m”, Team.class).getResultList();
    • 조인 쿼리가 나갑니다. 하지만 sql이랑 최대한 비슷하게 사용해야 합니다. 
    •  
    String jpql = “select m.team from member m join m.team team”;
    List<Team> result = em.createQuery(jpql, Team.class).getResultList();

    따라서 경로표현식인 과 같이 명시적으로 조인을 표현해줘야 합니다.

     

    경로 표현식 : (.)점을 찍어서 객체 그래프 탐색하는 것이다.

    상태필드, 연관필드등이 있다
    select m.username //상태 필드
    	from member m
    	join m.team t // 단일 값 연관 필드(manytooe,onetoone..)
    	join m.orders o // 컬렉션 값 연관 필드
    where t.name = "팀A"
    //상태필드로 가냐, 단일 값 연관 필드로 가냐, 컬렉션 값 연관 필드로 가냐에 따라서 결과가 달라짐.
    @Entity
    public class Member {
        @Id @GeneratedValue
        pirvate Long Id;
        
        @Column(name="name")
        private Integer age; // 상태필드
        private String username; // 상태필드
        
        @ManyToOne(..)
        private Team team; // 연관필드(단일 값 연관 필드 )
        
        @OneToMany(..)
        private List<Order> orders; // 연관필드(컬렉션 값 연관 필드 )

     

    필드들의 특징

    1.  상태 필드 경로 : 경로 탐색 끝 ( m.username 하고 또 .을 찍어서 어딜 갈 수가 없음. 끝임 )
    2. 단일 값 필드 경로 : 묵시적으로 내부 조인 ( 묵시적 조인 ) - 탐색 계속 가능 ( @ManyToOne, @OneToOne, 대상이 엔티티 )
    3. 컬렉션 값 필드 경로 : 더는 탐색 불가, FROM절에서 별칭 받으면 탐색 가능

     

    결론, 묵시적 조인은 내부조인에서만 가능하지만 명시적으로 JOIN 직접 적어주기

    명시적 조인? : 조인 키워드 직접 사용
    묵시적 조인? 경로 표현식에 의해 묵시적으로 sql조인 발생. 외부조인 불가능

     

    진짜 결론 : 경로 탐색을 사용한 묵시적 조인의 주의사항
    1) 묵시적 조인은 항상 내부 조인
    2) 컬렉션은 항상 경로 탐색의 끝, 명시적 조인을 통해 별칭을 얻어야.
    3) 경로 탐색은 select where에서만 사용하지만 sql에서는 from, where에 영향

    - 가급적 묵시적 조인 대신에 명시적 조인 사용합시다.
    - 조인은 sql 튜닝에 대한 중요 포인트.
    - 묵시적 조인은 조인이 일어나는 상황 자체에 대한 판단이 어려움.

     

     

    임베디드 타입 프로젝션

    임베디드 타입은 조회의 시작점이 될 수 없다.
    em.createQuery(“select o.address from Order o”,Address.class) - 임베디드 타입 프로젝션
    • 임베디드 타입으로만은 안되고, 그거가 속해있는 엔티티로부터 시작해야함.

     

    중복 제거하는 방법

    // 중복 제거
    DISTINCT 사용하기

     

    2) 프로젝션 - 여러 값 조회

    전체 대상이 아니라 꼭 필요한 데이터만 선택해서 조회하는 방법

    TypeQuery는 사용이 불가능하고, Query만 사용할 수 있다.

    List resultList = em.createQuery(“select m.username, m.age from member m”) .getResultList();

    1. 쿼리타입으로 조회 

    Object o = resultList.get(0);
    Object[] result = (Object[]) 0;

    2. 오브젝트 배열 타입으로 조회

    List<Object[]> resultList = em.createQuery(“select m.username, m.age from member m”) .getResultList();

    3. new 명령어로 조회

    -> 실제 Application에서는 개발시 Object[] 사용X, 의미있는 객체(memberDTO)로 변환해서 사용

    memberDTO를 만듬.

    //Member DTO class
    public class MemberDTO {
    	private String username;
    	private String age;
    
    	//getter()
    	//setter()
    }
    
    //test file
    List<MemberDTO> result = em.createQuery(“select new jpql.MemberDTO(m.username, m.age) from member m”, 
        MemberDTO.class)
        .getResultList();
    주의
    1. 패키지 명을 포함한 전체 클래스 명입력 필요 ( 패키지 이름을 new에다가 다 해줘야됨 )
    2. 순서와 타입이 일치하는 생성자가 필요함.

     

    5. 페이징 API

    JPA는 페이징을 다음 두 API로 추상화

    setFirstResult(int startPosition) : 조회 시작 위치
    setMaxResult(int maxResult) : 조회할 데이터

    페이징 쿼리는 정렬조건이 중요하다

     

    예시

    em.createQuery(“select m from member m order by m.age desc”) - 페이징에서는 오더바이가 들어가야’
    	.setFirstResult(1)
    	.setMaxResult(10)
    	.getResult();

    페이징 SQL을 더 최적화하고 싶다면 JPA가 제공하는 페이징API  가 아니라 네이티SQL을 직접 사용해야 한다

     

     

    6. 조인 : 엔티티 중심으로 조인 문법이 나감.

    내부조인 : select m from member m [inner] join m.team t 회원과 연관있는 team을 조인할거야

    * 서로 다른 타입의 두 엔티티를 조회했으므로 TypeQuery 사용이 불가하다

    String query = “select m from member m inner join m.team t where t.teamname = :teamName” 이런식으로

    외부조인 : select m from member m left join m.team t

    String query = “select m from member m left join m.team t where t.teamname = :teamName” 이런

     

    컬렉션 조인 ( xxxToOne ) : 컬렉션 값 연관 필드 사용

    컬렉션 조인: 일대다, 다대다 관계처럼 컬렉션을 사용하는 곳에 조인하는 것을 말한다 ( 외부조인에 사용함 )

     

     

    세타조인 : 연관관계가 전혀 없는 애를 하고 싶을 때.

    -> 이제는 서로 아무 연관관계가 없어도 left join 가능

    String query = “select m from member m, team t  where m.username = t.teamname”
    • on 절 : jpa 2.1부터 지원 ( where 절 사용할때랑 결과가 똑같음 )
      조인 대상 필터링
      ex) 회원과 팀을 조인하면서 팀 이름이 a인 팀만 조인
      jpql: select m,t from member m left join m.team t on t.name = ‘a’

     

    페치조인 ( 제일 중요 ) : 성능 최적화를 위해 존재함

    -> 연관된 엔티티니 컬렉션을 한번에 같이 조회하는 기능인데 join fetch 명령어 사용

    패치조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로

     

    패치 조인의 예시

        List<Board> resultList = em.createQuery(
                        "select b from Board b join fetch b.writer w", Board.class)
                        .getResultList();

     

    패치 조인은 SQL 호출수를 줄여서 성능을 최적화한다

    패치는 (fetch.LAZY) 같은 글로벌 로딩 전략보다 우선순위가 높다

     

    결론, 글로벌 로딩 전략은 될수있음 지연(LAZY)로딩을 사용하고, 최적화가 필요한 곳에 패치조인을 사용하는것이 효과적이다.

     

    단점
    1. 지연로딩 불가능 (LAZY) 
    2. 별칭 사용 불가
    3. 더 이상 컬렉션을 패치할 수 없다.
    4. 컬렉션을 패치 조인하면 페이징API 사용 불가능 ( 성능 이슈, 메모리초과 문제 발생가능)
        ( 컬렉션 ( 일대다 ) 가 아니라 단일 값 연관 필드는 패치 조인하고 페이징 API 사용 가능 )

    패치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.

    반면에 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면 여러 테이블에서 필요한 필드들만 조회해서 DTO로 반환하는 것이 더 효과적일 수 있다. 

     

     

    패치 조인 vs 일반 조인 간단 정리

    1. 일반 조인
      • 연관 엔티티에 join을 하게되면 Select 대상의 엔티티는 영속화하여 가져오지만, 조인의 대상은 영속화하여 가져오지 않는다.
      • 연관 엔티티가 검색 조건에 포함되고, 조회의 주체가 검색 엔티티뿐일 때 사용하면 좋다.
    2. 패치 조인
      • 연관 엔티티에 fetch join을 하게되면 select 대상의 엔티티뿐만 아니라 조인의 대상까지 영속화하여 가져온다.
      • 연관 엔티티까지 select의 대상일 때, N+1의 문제를 해결하여 가져올 수 있는 좋은 방법이다

     

    7. 서브쿼리

    jpa는 where,having 절에서만 서브 쿼리 사용 가능 ( select, from 은 안되고, 하이버네이터에서는 select 지원 )

     

    서브쿼리 함수

    [NOT] EXISTS
    {ALL | ANY | SOME }
    [NOT] IN

    나이가 평균보다 많은 회원

    select m from member m where m.age > (select avg(m2.age) from member m2)
    한 건이라도 주문한 고객
    select m from member m where (select count(0) from Order o where m = o.member)>0

     

    8. 타입 표현 ( 조건식 )

    1. 타입 표현

    - 대소문자 구분안함

     

    2. 연산자 우선순위 있음

    - 경로 탐색 연산 > 수학연산 > 비교연산 > 논리연산

     

    3. 논리 연산과 비교식

    - AND | OR | NOT

    - = | > | >= | < | <= | <>

     

    4. BETWEEN, IN, Like, NULL비교 있음

    - Like 는 % , _ 을 사용해서 패턴값과 문자표현식 비교

    - NULL은 = 가 아니라 IS NULL 로 비교

     

    9. 조건식 

    1. 기본 케이스식

    CASE
        { WHEN <조건식> THEN <스칼라식>} +
        ELSE <스칼라식>
    END
    
    예
    select
        case when m.age <= 10 then '학생요금'
             when m.age >= 60 then '경로요금'
             else '일반요금'
        end
    from Member m

     

    2. 단순 케이스식 ( switch 문과 유사 )

    CASE < 조건 대상 >
        { WHEN <스칼라식1> THEN <스칼라식2>} +
        ELSE <스칼라식>
    END
    
    예
    select
        case t.name
             when '팀A' then '인센 110%'
             when '팀B' then '인센 120%'
             else '인센 105%'
        end
    from Team t


    3. coalesce : 스칼라식 하나씩 조회해서 Null이 아니면 반환
    4. nullif : 두 값이 같으면 null 반환, 다르면 첫 번째 값 반환

     

     

    10. jpql 함수 - 기본함수/사용자정의함수

    1) 기본함수 : 디비에 상관없이 jpql이 지원하는 함수.
    2) 사용자 정의 함수 : 디비에 함수가 있으면 그걸 가져다가 써야 하는데, jpql은 그 함수를 알 방법이 없음
    select function(‘group_concat’,i.name) from item i
    그런 함수들은 디비에 종속적이지만, 함수들이 등록이 되어져있다

    //ex)concat
    String query = “select ‘a’ || ‘b’ from member m”  = concat(‘a’,’b’)
    //ex) substring
    String query = “select substring(m.username,2,3) from member m” …
    //ex) locate
    String query = “select locate(‘de’,’abcdefg’) from member m”
    //‘de’의 자리 4가 나옴. 

    ex) length, abs, sqrt, mod

    ex) size, index (jpa 용도)

    String query = “select size(t.members) from team t” - 컬렉션의 크기를 돌려줌
    String query = “select index(t.members) from team t”

    '스프링' 카테고리의 다른 글

    [스프링] 트랜잭션과 락,2차캐리  (0) 2022.12.28
    [스프링] OSIV  (2) 2022.12.28
    [스프링] 스프링 데이터 JPA  (0) 2022.12.28
    [스프링] 객체지향 쿼리 심화  (0) 2022.12.26
    [스프링] QueryDsl 정리  (0) 2022.12.26
Designed by Tistory.