ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] JPA 쿼리 지원 방식(JPQL)
    JPA 2020. 3. 30. 02:43
    반응형

    JPA 쿼리 지원 방식


    JPA는 데이터베이스에 데이터를 주고받기 위한 다양한 쿼리 방식을 지원한다.

     

    1. JPQL
      • 가장 단순한 조회 방법
        • EntityManager.find()
        • 객체 그래프 탐색(a.getB().getC())
      • 테이블이 아닌 매핑된 엔티티를 대상으로 검색(SQL은 테이블을 대상으로 검색)
      • JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어를 제공
      • ANSI 표준 SQL을 지원하는 문법 모두 적용이 가능
      • SQL을 추상화해서 특정 데이터베이스에 의존하지 않는다.
      • 단순 String 값으로 쿼리가 쓰이기 때문에 동적 쿼리를 만들기 어렵다.
    2. Criteria
      • 쿼리를 String 문자가 아닌 자바 코드로 작성하기 때문에 컴파일 오류를 찾기 쉬움
      • 동적 쿼리를 짜기 수월하지만 코드의 가독성이 매우 떨어져 유지보수가 어려움
      • 사용 빈도가 굉장히 낮음
    3. QueryDSL
      • 오픈소스
      • 쿼리를 String 문자가 아닌 자바 코드로 작성하기 때문에 컴파일 오류를 찾기 쉬움
      • 동적쿼리 작성이 수월하고 코드가 직관적이고 쉽다.
      • 실무에서 많이 사용되는 방식
    4. 네이티브 SQL
      • JPA가 직접 SQL을 사용하는 기능
      • JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능을 사용할 때 쓰임

     

    JPQL(Java Persistence Query Language)


    JPQL은 객체지향 쿼리 언어이므로 테이블을 대상으로 쿼리 하는 것이 아닌 엔티티 객체를 대상으로 쿼리 한다, 또한 SQL을 추상화해서 특정 데이터베이스에 종속되지 않고 사용할 수 있다.

    select m from Member as m where m.name = "devyu"

    Member는 테이블이 아닌 엔티티 객체이며 대소문자를 구분함을 유의하자. 

    별칭은 꼭 사용되어야 하고 as는 생략이 가능하다. ANSI SQL에 따른 표준 함수들도 사용할 수 있다.(ex, SUM, AVG, MAX, MIN, COUNT 등)

     

    TypeQuery와 Query

    TypeQuery는 반환 타입이 명확할 때 사용되고 Query는 반환타입이 명확하지 않을 때 사용된다.

    // TypedQuery 타입 정보를 받을 수 있을 때(Member.class 명시)
    TypedQuery<Member> createQuery = em.createQuery("select m from Member m", Member.class);
    // TypedQuery 타입 정보를 받을 수 있을 때(Integer.class 명시)
    TypedQuery<Integer> createQuery2 = em.createQuery("select m.age from Member m", Integer.class);
    
    // Query 타입 정보를 받을 수 없을 때(반환값이 int와 String)
    Query createQuery = em.createQuery("select m.age, m.username from Member m");

     

    조회 API

    • query.getResultList();
      • 결과가 하나 이상일 때, 리스트 반환
      • 결과가 없다면 빈 리스트
    • query.getSingleResult();
      • 결과가 단 하나뿐일 때, 단일 객체 반환
      • 결과가 없다면 javax.persistence.NoResultException 발생
      • 결과가 둘 이상이라면 javax.persistence.NonUniqueResultException 발생

    파라미터 바인딩

    • 이름 기준 바인딩
    em.createQuery("select m from Member11 m where m.username=:username")
                  .setParameter("username", "devyu")
                  .getSingleResult();

    바인딩하려는 위치에 ' : ' 를 사용하고 바인딩 이름을 적는다. setParameter() 메서드에 바인딩 이름과 실제 적용될 값을 파라미터로 넘겨준다.

     

    • 위치 기준 바인딩
    em.createQuery("select m from Member11 m where m.username=?1")
                  .setParameter(1, "devyu")
                  .getSingleResult();

    바인딩하려는 위치에 ' ? ' 를 사용하고 바인딩 순서를 적는다. setParameter() 메서드에 바인딩 순서와 실제 적용될 값을 파라미터로 넘겨준다. 위치에 민감하기 때문에 사용을 실수가 나올 수 있는 바인딩 방법이다.

     

    프로젝션

    프로젝션이란 SELECT절에 조회할 대상을 지정하는 것이며 프로젝션의 대상은 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터를 의미)이 있다.

    @Entity
    public class Member{
    
    	@Id @GeneratedValue
    	@Column(name =  "MEMBER_ID")
    	private Long id;
    	private String username;
    	private int age;
        
    	@Embedded
    	private Address address;
    
    	@ManyToOne
    	@JoinColumn(name = "TEAM_ID")
    	private Team team;
    }
    // 엔티티 프로젝션
    select m from Member m
    
    // 엔티티 프로젝션
    select m.team from Member m
    
    // 임베디드 타입 프로젝션
    select m.address from Member m
    
    // 스칼라 타입 프로젝션
    select m.username, m.age from Member m

    SELECT를 통해 조회된 엔티티는 영속성 컨텍스트 속에서 관리된다. 엔티티 타입으로 데이터를 받고 조작할 수 있다.

     

     

    그런데 숫자, 문자 등 여러 타입이 겹친 스칼라 타입 프로젝션의 경우 어떤 타입으로 데이터를 전달받을까?

     

    • Object[] 타입으로 받기
    // 단순 값 받기 Object[]
    List<Object[]> resultList = em.createQuery("select m.age, m.username from Member m")
    		.getResultList();
    // age
    System.out.println(resultList.get(0)[0]);
    // username
    System.out.println(resultList.get(0)[1]);

     

    • new 명령어를 통한 DTO로 받기
    public class MemberDTO {
    
    	private int age;
    	private String username;
    	
        // 생성자 순서 중요
    	public MemberDTO(int age, String username) {
    		this.age = age;
    		this.username = username;
    	}
    	
        // getter, setter...
    }
    
    List<MemberDTO> resultList = 
    	em.createQuery("select new 패키지경로.MemberDTO(m.age, m.username) from Member m", MemberDTO.class)
    	.getResultList();
    
    // age
    System.out.println(resultList2.get(0).getAge());
    // username
    System.out.println(resultList2.get(0).getUsername());
    

     

    패키지 명을 포함한 전체 클래스명을 new 명령어를 통해 DTO 생성하여 값을 넣는 방법. 순서와 타입이 일치하는 생성자가 필수적으로 존재해야 한다.

     

    페이징

    JPA에서 페이징 API는 굉장히 간단하게 구현할 수 있다.

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

    setFirstResult() 메서드에 파라미터로 조회 시작 위치를 넣어주고 setMaxResults() 메서드의 파라미터로 조회할 데이터의 수를 넣어주기만 하면 된다. 

     

    사용하는 데이터베이스 방언 종류에 따라 페이징 관련 쿼리가 다르게 전달된다. 예를 들어 MySQL의 경우 limit와 offset을 이용한 쿼리가 나가고 Oracle의 경우 ROWNUM을 이용한 3중 뎁스로 쿼리가 나간다.

    JPA 방언으로 지정된 데이터베이스의 종류에 따라 (MySQL(좌), Oracle(우)) 같은 코드임에도 다르게 나간다.

     

    조인

    내부조인(대괄호 생략가능)

    select m from Member m [inner] join m.team t

     

    외부조인(대괄호 생략가능)

    select m from Member m left [outer] join m.team t

     

    ON절 활용 조인

    • 조인 대상 필터링
    • 연관관계가 없는 엔티티 외부 조인 가능
    // 회원과 팀을 조인하면서 팀이름이 'IT사업부'인 팀만 조인
    
    // JPQL(연관관계 O)
    select m, t from Member m left join m.team t on t.name = 'IT사업부'
    
    // SQL
    select m.*, t.* from Member m left join Team on m.team_id = t.id and t.name = 'IT사업부'
    
    // 회원의 이름과 팀의 이름이 같은 대상 외부 조인
    
    // JPQL(연관관계 X)
    select m, t from Member m left join Team t on t.name = 'IT사업부'

     

    서브쿼리

    // 나이가 평균보다 많은 회원 조회
    select m from Member m where m.age = (select avg(m2.age) from Member m2)
    
    // 한 건이라도 주문한 회원 조회
    select m from Member m where (select count(o) from Order o where m = o.member) > 0

    JPA 표준 스펙은 WHERE절과 HAVING절에만 서브쿼리 사용가능하지만 Hibernate는 SELECT절까지 서브쿼리를 사용할 수 있다.

    FROM절의 서브쿼리는 JPQL에서 불가능함.

     

    JPQL 타입 표현

    문자

    • 'Hello'
      • single quartation 안쪽에 문자열을 표현
    • 'She''s'
      • single quartation 안쪽 문자열에 single quartation이 사용된다면 한개 더 사용해서 이스케이프 문자를 만든다.

    숫자

    • 10L
      • long형
    • 10D
      • double형
    • 10F
      • float형

    ENUM

    • xyz.devyu.MemberType.Admin
      • 자바 패키지명을 포함해서 넣어야 한다
    반응형

    댓글

Designed by Tistory.