JPA JPQL

JPQL소개 및 다른 쿼리 API

  • 가장 단순한 조회 방법
  • 엔티티 객체를 중심으로 개발
  • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL필요
  • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
List<Member> result = em.createQuery(
        "select m from Member m where m.username like '%kim%'",
        Member.class
).getResultList();
  • 엔티티를 대상으로 쿼리 실행
  • 특정 데이터베이스 SQL에 의존하지 않음

제약

  • 해당 쿼리는 문자열이기 때문에 동적 쿼리를 만들기 어렵다

Criteria

  • JPQL의 동적쿼리 등 더 나은 기능을 제공
  • 자바 코드로 짜기 때문에 컴파일시 오류를 체크할 수 있음
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);

Root<Member> m = query.from(Member.class);

CriteriaQuery<Member> cq = query.select(m);

String username = "sdfsd";
if(username != null){
    cq = cq.where(cb.equal(m.get("username"),"kim"));
}
List<Member> result = em.createQuery(cq).getResultList();
  • 단!! 알아보기 어렵고 유지보수가 쉽지 않아 실무에서 사용하기 힘듬
  • SQL 스럽지 않다.

따라서 Criteria 대신에 QueryDSL 사용 권장

QueryDSL

  • 위의 두개의 제약을 모두 해결
  • 실무에서 많이 사용

JPQL 실제 사용

  • 엔티티 속성은 대소문자 구분
  • JPQL 키워드는 대소문자 구분안함 (select, from)
  • 테이블 이름이 아닌 엔티티 이름이다.
  • 별칭 필수 (as 생략가능)

TypeQuery, Query

  • TypeQuery : 반환 타입이 명확할 때 사용
  • Query : 반환 타입이 명확하지 않을때 사용
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
TypedQuery<String> query1 = em.createQuery("select m.username from Member m", String.class);
Query query2 = em.createQuery("select m.username, m.age from Member m");

결과조회

  • query.getResultList() : 결과 없으면 빈 리스트 반환 (결과 다수)
  • query.getSingleResult() : 결과가 없거나 둘이상이면 예외 발생
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
List<Member> resultList = query.getResultList();
Member result = query.getSingleResult();

파라미터 바인딩

List<Member> resultList = em.createQuery("select m from Member m where m.username = :username", Member.class)
                .setParameter("username","member1")
                .getResultList();

프로젝션

  • select 절에 조회할 대상을 지정하는것
  • 엔티티로 반환이 되면 모두 영속성으로 관리된다.
여러값 조회시 (방법 1)
List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m")
        .getResultList();

Object[] result = resultList.get(0);
여러값 조회시 (방법 2)
  • new 명령어로 dto로 조회
  • dto의 주소값을 전부 적어줘야한다. (new jpql.MemberDTO) 그리고 생성자도 있어야 객체로 생성하여 값을 받을 수 있다.
List<MemberDTO> resultList = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m", MemberDTO.class)
        .getResultList();

MemberDTO memberDTO = resultList.get(0);
System.out.println("memberDTO = " + memberDTO.getUsername());
System.out.println("memberDTO = " + memberDTO.getAge());

페이징 API

  • 두개의 API로 추상화
  1. setFirstResult(int startPosition): 조회 시작 위치 (0부터)
  2. setMaxResults(int maxResults) : 조회할 데이터 수
List<Member> resultList = em.createQuery("select m from Member m order by m.age desc ", Member.class)
        .setFirstResult(1)
        .setMaxResults(10)
        .getResultList();
  • H2 방언 기준으로 페이지 , (각 DB 방언에 따라 sql을 만들어서 페이징 해준다)
select
    member0_.id as id1_0_,
    member0_.age as age2_0_,
    member0_.TEAM_ID as TEAM_ID4_0_,
    member0_.username as username3_0_
from
    Member member0_
order by
    member0_.age desc limit ? offset ?

조인

  • 내부, 외부, 세타 조인 모두 가능
  1. 내부 조인 ([inner] join을 사용하여 조인가능)
Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");
member.setAge(10);

member.setTeam(team);

em.persist(member);

em.flush();
em.clear();

String query = "select m from Member m inner join m.team t";

List<Member> resultList =
        em.createQuery(query, Member.class)
        .getResultList();

주의: Member에서 Team은 즉시로딩이 기본이므로 지연로딩으로 바꿔야 select 문이 하나만 실행된다.

select
    member0_.id as id1_0_,
    member0_.age as age2_0_,
    member0_.TEAM_ID as TEAM_ID4_0_,
    member0_.username as username3_0_
from
    Member member0_
inner join
    Team team1_
        on member0_.TEAM_ID=team1_.id
  1. 외부 조인 (left join을 사용하여 조인)
String query = "select m from Member m left join m.team t";
select
    member0_.id as id1_0_,
    member0_.age as age2_0_,
    member0_.TEAM_ID as TEAM_ID4_0_,
    member0_.username as username3_0_
from
    Member member0_
left outer join
    Team team1_
        on member0_.TEAM_ID=team1_.id
  1. 세타 조인 String query = "select m from Member m, Team t where m.username = t.name";
select
    member0_.id as id1_0_,
    member0_.age as age2_0_,
    member0_.TEAM_ID as TEAM_ID4_0_,
    member0_.username as username3_0_
from
    Member member0_ cross
join
    Team team1_
where
    member0_.username=team1_.name

조인 대상 필터링

  • 예) 회원과 팀을 조인할때 팀 이름이 A인 팀만 조인
  • on을 사용해서 조인

연관관계 없는 엔티티 외부 조인

  • 예) 회원의 이름과 팀의 이름이 같은 대산 외부 조인

서브 쿼리

  • EXISTS : 서브쿼리에 결과가 존재하면 참
    • ALL : 모두 만족하면 참
    • ANY, SOME: 같은의미, 조건을 하나라도 만족하면 참
  • IN : 하나라도 같으면 참
서브 쿼리 한계
  • JPA는 where, having 절에서 서브쿼리 사용 가능
  • select 절에서 사용가능
  • From 절의 서브쿼리는 현재 JPQL에서 불가능 (대부분 조인으로 풀어서 해결 )

JPQL 타입 표현

  • 문자, 숫자, Boolean은 그대로 사용
  • ENUM 은 패키지명을 포함하여 적어줘야한다.
String query = "select m from Member m where m.type = jpql.MemberType.ADMIN";

List<Member> resultList =
        em.createQuery(query, Member.class)
        .getResultList();

조건식 - CASE

  • 기본 case 식 모두 사용 가능
  • COALESCE: 하나씩 조회해서 null이 아니면 반환
  • nullif: 두 값이 같으면 null 반환, 다르면 처번째 값 반환

기본 함수

  • jpa에서 제공하는 표준 함수
  • DB상관없이 사용 가능
  • 일반적으로 아는 concat, subString, trim, lower, upper, length, locate, ABS,SQRT,MOD
  • size (컬렉션 크기를 돌려준다)

사용자 정의 함수 호출

  • DB에 함수가 정의가 되어있다고 가정
  • jpa에 사용하는 dB 방언을 상속받고, 사용자 정의 함수를 등록해서 사용 select function('등록한 함수명', 파라미터) from table

경로 표현식

  • 상태 필드 : 단순히 값을 저장하기 위한 필드 (ex : m.username)
  • 연관 필드 : 연관 관계를 위한 필드
  1. 단일 값 연관 필드 : @ManyToOne, @OneToOne, 대상이 엔티티 (ex : m.team)
  2. 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany, 대상이 컬렉션 (ex : m.orders)

특징

  • 상태 필드 :경로 탐색의 끝, 더이상 탐색이 안됨
  • 단일 값 연관 경로 : 묵시적 내부 조인 발생(객체에서는 단순 from에서 객체를 탐색했지만 DB는 조인 발생) , 탐색 가능
String query = "select m.team from Member m";
  • DB는 조인 발생… 주의 해서 사용
select
    team1_.id as id1_3_,
    team1_.name as name2_3_
from
    Member member0_
inner join
    Team team1_
        on member0_.TEAM_ID=team1_.id
  • 컬렉션 값 연관 경로 : 묵시적 내부 조인 발생, 더이상 탐색 안됨

    From 절에서 명시적 조인을 통해 별칭을 얻어 탐색 가능

실무에서는 묵시적 조인 사용 X , 전부 명시적 조인을 사용 권장