본문 바로가기

Java/JPA

JPQL-프로젝션,조인,조건식

애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요하다.

JPQL은 SQL과 문법이 유사함. 엔티티 객체를 대상으로 쿼리.

JPQL문법

select_문 :: ==

select_절

from_절

[where_절]

[groupby_절]

[having_절]

[orderby_절]

update_문 :: = update_절[where_절]

delete_문 :: = delete_절[where_절]

엔티티와 속성은 대소문자 구분o(Member, age)
JPQL키워드는 대소문자 구분x(select)
엔티티 이름 사용, 테이블 이름이 아님.

별칭은 필수(Member m)

TypeQuery, Query

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

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

          TypedQuery<Member> query = 
                  em.createQuery("SELECT m FROM Member m", Member.class);
          Query query = 
                  em.createQuery("select m.username, m.age from Member m");

결과 조회 API

-query.getResultList() : 결과가 하나 이상일때, 리스트 반환, 결과가 없으면 빈 리스트 반환
-query.getSingleResult() : 결과가 정확히 하나, 객체 반환, 결과가 없어도 에러, 둘 이상이어도 에러,

파라미터 바인딩

        Query query = em.createQuery("select m from Member m where m.username=:username");
                    query.setParameter("username","호호");

검색예시

        String jpql = "select m from Member m where m.name like '%hello%'";
        List<Member> result = em.createQuery(jpql,Member.class)
                .getResultList();

검색예시2

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

image

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

대상 :

  • select m from Member m -> 엔티티 프로젝션
  • select m.team from Member m -> 엔티티 프로젝션
  • select m.address from Member m ->임베디드 타입 프로젝션

    DISTINCT로 중복 제거 가능

embedded타입은 from절에 기준 엔티티를 적어줘야함.

List<Address> result = em.createQuery("select o.address from Order o", Address.class)
        .getResultList();

프로젝션 - 여러 값 조회

ex) select m.username, m.age from Member m

방법 3가지

  1. Query타입으로 조회
  2. Object[]타입으로 조회
  3. new 명령어로 조회
  • 단순 값을 DTO로 바로 조회한다.
  • 패키지명을 포함한 전체 클래스명을 입력해야함.
  • 순서와 타입이 일치하는 생성자가 필요하다

전체 클래스명 입력하는거 귀찮으면 queryDsl 써야댐. 필드 순서 생성자대로. 틀리면 안됨

List<MemberDTO> result = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m",MemberDTO.class)
        .getResultList();

toString()만들때 주의) 무한 루프 만들 수 있어서 양방향 컬럼은 지움.

@Entity @Getter @Setter
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String username;
    private int age;
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    @Override
    public String toString() {
        return "Member{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", age=" + age +
                '}';
    }

세타조인(=막조인) : 마구잡이 조인

Hibernate 6.1이후부터 from절 subquery도 가능해짐.

조건식 - case

String query =
        "select " +
                "case when m.age <= 10 then '학생요금' " +
                "     when m.age >=60 then '경로요금' " +
                "     else '일반요금' " +
                "end " +
        "from Member m";
List<String> result = em.createQuery(query, String.class)
        .getResultList();
for (String s : result) {
    System.out.println("s = " + s);
}

조건식 - nullif() : 관리자 이름 숨길때? 쓸 수 있다(두개 값이 같을 때 null 반환)

String query = "select nullif(m.username, '관리자') as username from Member m";
List<String> result = em.createQuery(query, String.class)
        .getResultList();
for (String s : result) {
    System.out.println("s = " + s);

}

사용자 정의 함수

1. H2Dialect 상속받은 클래스 생성

package dialect;

import org.hibernate.dialect.H2Dialect;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.type.StandardBasicTypes;

public class MyH2Dialect extends H2Dialect {
    public MyH2Dialect() {
        registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
    }
}

2. persistence.xml에 dialect 경로 수정

<property name="hibernate.dialect" value="dialect.MyH2Dialect"/>

쿼리 이렇게 써도 하이버네이트가 지원해줌.


String query = "select group_concat(m.username) from Member m";

묵시적 조인 쓰지말고 명시적 조인을 써야 쿼리 튜닝하기 좋다.