JPA JPQL 작성의 모든 것!!


JPA 의 EntityManager.find() 기능으로 내가 원하는 검색을 하기에는 어려운 점이 많습니다. 다양한 조회 조건으로 검색을 해야 원하는 결과를 얻을 수 있습니다.
이럴 경우 엔티티 객체를 대상으로 검색을 할 수 있도록 하는 방법이 JPQL(Java persistence Query Language) 입니다. JPQL은 아래와 같은 방법으로 작성 할 수 있습니다.

기능 설명
JPQL  
Criteria 쿼리 JPQL 작성을 도와주는 API 클래스 패키지
네이티브 SQL SQL을 직접 사용 할 수 있도록 도와주는 패키지
QueryDSL Criteria 와 같은 JPQL 작성을 도와주는 오픈소스 API
JDBC, MyBatis 등 사용  

JPQL 기본 사용 방법


JPQL 은 데이터 베이스를 조회 하는 SQL이 아니라 객체를 조회 하는 객체 지향 쿼리입니다. 작성 방법은 SQL 작성과 비슷 합니다.
조회 하고자 하는 FROM 절은 엔티티명이 되며 WHERE 절은 엔티티 필드가 됩니다.

  • 예제 공통 : 회원 정보 엔티티
@Entity
@Table(name="MEMBER")
public class Member {
    @Id @GeneratedValue
    @Column(name = "MEMBER_ID")
    private long id;
    
    @Column(name = "MEMBER_NAME")
    private String name;

    @Embedded Period membershipPeriod;		//회원 등록 기간
    @Embedded Inbody memberInbody;			//회원 신체 정보
}



1. 동적 쿼리 사용 방법


  • 기본 조회

      private static void findMember(EntityManager em) {
          String findQuery = "select m from Member as m where m.name = '홍길동'";
          List<Member> resultList = em.createQuery(findQuery, Member.class).getResultList();
          for (Member member : resultList) {
              System.out.println(member.toString());
          }
      }
        
      private static void findMember(EntityManager em) {
          String findQuery = "select m.id, m.name from Member as m where m.name = '홍길동'";
          List<Member> resultList = em.createQuery(findQuery, Member.class).getResultList();
          for (Member member : resultList) {
            System.out.println(member.toString());
          }
      }
    
  • TypedQuery, Query

    • 쿼리 객체를 생성 할 경우 TypedQuery, query 중 상황에 맞게 사용 할 수 있습니다.
      //반환 타입이 Member 로 명확한 경우 TypedQuery 사용
      private static void findMember(EntityManager em) {
          TypedQuery<Member> findQuery = em.createQuery("select m from Member m where m.name = '홍길동'", Member.class);
          List<Member> resultList = findQuery.getResultList();
            
          for (Member member : resultList) {
              System.out.println(member.getId());
              System.out.println(member.getName());
          }
      }
      
      //타입이 명확하지 않는 컬럼의 조회를 할 경우 TypedQuery 를 사용 하지 못하여 Object 를 사용 함
      private static void findMember(EntityManager em) {
          String findQuery = "select m.id, m.name from Member m where m.name = '홍길동'";
          List resultList = em.createQuery(findQuery).getResultList();
          for (Object o : resultList) {
            Object[] result = (Object[]) o;     //결과가 둘이상이며 타입이 명확 하지 않을 경우 Object 를 사용 하여 반환한다.
            System.out.println("id   : "+result[0]);
            System.out.println("name : "+result[1]);
          }
      }
    
  • 데이터 바인딩

      //데이터 바인딩
      private static void findMember(EntityManager em, String memberName) {
          String findQuery = "select m from Member as m where m.name = :memberName ";	
          List<Member> resultList = em.createQuery(findQuery, Member.class)
                  .setParameter("memberName", memberName)   //데이터 바인딩
                  .getResultList()
                  ;
      }
    
  • 페이징 API

      private static void findMember(EntityManager em, String memberName) {
          String findQuery = "select m from Member as m order by m.id desc ";	
          List<Member> resultList = em.createQuery(findQuery, Member.class)
                  .setFirstResult(0)    //조회 시작 위치
                  .setMaxResults(10)    //조회 할 건수
                  .getResultList()
                  ;
      }
    
  • JOIN(INNER, LEFT)

      private static void findMember(EntityManager em, String teacherName) {
          String findQuery = "select m from Member as m INNER JOIN m.teacher t WHERE t.name = :teacherName ";	
          List<Member> resultList = em.createQuery(findQuery, Member.class)
                  .setParameter("teacherName", teacherName)
                  .getResultList()
                  ;
      }
    
  • Fetch JOIN(페치 조인)

    • 페치 조인은 JQPL 에서 성능 최적 화를 위해서 제공 하는 기능으로 연관된 엔티티나 컬렉션을 한번에 같이 조회 하는 기능입니다.
      이 기능은 지연로딩 과 관련이 있으며 조회를 할때 지연 로딩이 일어나지 않고 조회 되기 때문에 프록시가 아닌 실제 엔티티를 조회 하게 됩니다.

    • 문법 : [LEFT[OUTER] | INNER] JOIN FETCH 조인경로(별칭 사용 안됨.)

      private static void findMember(EntityManager em, String teacherName) {
          String findQuery = "select m from Member as m JOIN FETCH m.teacher ";	
          List<Member> resultList = em.createQuery(findQuery, Member.class).getResultList();
      }
    
  • 컬렉션 페치 조인

    • 다대일 관계인 회원과 강사 엔티티를 컬렉션 페치 조인하여 조회 해보겠습니다. “홍길동” 회원에게 배정된 강사의 목록을 조회 해도
      지연 로딩이 발생 되지 않고 출력이 되는 것을 확인 할 수 있습니다.
      private static void findMember(EntityManager em, String teacherName) {
          String findQuery = "select m from Member m JOIN FETCH m.teacher where m.name = '홍길동' ";	
          List<Member> resultList = em.createQuery(findQuery, Member.class).getResultList();
            
          for (Member m : resultList) {
              System.out.println(m.getName());
                
              for (Teacher t : m.getTeacher()) {
                  System.out.println(t.getName());
              }
          }
      }
    



2. @NamedQuery 를 이용한 정적 쿼리 사용 방법

앞서 em.createQuery(query) 처럼 사용 하는 방법을 동적 쿼리라고 하고 쿼리를 미리 정의 하여 이름으로 가져와서 사용 하는 방법이 있는데 이 방법을 정적 쿼리하고 합니다. 정적 쿼리를 사용 할때는 @NamedQuery 어노테이션을 사용 하여 이름과 쿼리를 지정 합니다.

NamedQuery 는 어플리케이션 로딩 시점에 JPQL 문법을 체크하여 미리 파싱 해놓기 때문에 오류 확인이 빠르고 성능상 이점과 최적화에도 도움이 됩니다. 정적 쿼리는 xml 로도 작성이 가능 합니다.

  • @NamedQuery 사용 방법

    • 회원 엔티티에 사용 할 NamedQuery 를 작성 합니다.
      @Entity
      @Table(name="MEMBER")
      @NamedQuery(name = "Member.findMemberByName", query="select m from Member m where m.name = :name ")
      public class Member {
      	@Id @GeneratedValue
      	@Column(name = "MEMBER_ID")
      	private long id;
        	
      	@Column(name = "MEMBER_NAME")
      	private String name;
      }
    
    • 조회
      private static void findMember(EntityManager em, String name) {
          List<Member> resultList = em.createNamedQuery("Member.findMember", Member.class)  //createNamedQuery 를 사용 하여 미리 작성 해둔 쿼리를 호출
                  .setParameter("name", name)
                  .getResultList();
      }
    
  • 다중 쿼리를 작성 할 경우에는 @NamedQueries 를 사용 하면 됩니다.

      @Entity
      @Table(name="MEMBER")
      @NamedQueries({
      	@NamedQuery(name = "Member.findMemberById", query="select m from Member m where m.id = :id "),
      	@NamedQuery(name = "Member.findMemberByName", query="select m from Member m where m.name = :name ")
      })
        
      public class Member {
      	@Id @GeneratedValue
      	@Column(name = "MEMBER_ID")
      	private long id;
        	
      	@Column(name = "MEMBER_NAME")
      	private String name;
      }
    



Criteria 를 이용한 쿼리 작성


Criteria 는 JPQL 을 자바 코드로 작성 할 수 있도록 도와주는 빌더 클래스 API입니다.
간단히 사용 방법을 통해 알아 보겠습니다.

  • 조회

      private static void findMember(EntityManager em, String name) {
          CriteriaBuilder cb = em.getCriteriaBuilder();	            //빌더 선언
          CriteriaQuery<Member> cq = cb.createQuery(Member.class); 	//생성, 반환 타입 지정
            
          Root<Member> m = cq.from(Member.class);	    //From 절
          cq.select(m)								//select 절
              .where(cb.equal(m.get("name"), name))	//where   절
            
          TypedQuery<Member> query = em.createQuery(cq);
          List<Member> members = query.getResultList();
      }
    
  • 정렬

          cq.select(m)						    //select 절
          .where(cb.equal(m.get("name"), name))	//where   절
          .orderBy(cb.desc(m.get("id")));			//order by절
    
  • JOIN

      private static void findMember(EntityManager em, String name) {
          CriteriaBuilder cb = em.getCriteriaBuilder();	            //빌더 선언
          CriteriaQuery<Object> cq = cb.createQuery(); 	//생성, 반환 타입 지정
            
          Root<Member> m = cq.from(Member.class);	//From 절
          Join<Member, Teacher> t = m.join("teacher", JoinType.INNER);	//JOIN 절
            
          cq.multiselect(m, t)						//select 절
              .where(cb.equal(m.get("name"), name))	//where   절
              .orderBy(cb.desc(m.get("id")));			//order by절
            
          List query = em.createQuery(cq).getResultList();
            
          for (Object object : query) {
              Object[] o = (Object[]) object;
              System.out.println(o[0]); //member
              System.out.println(o[1]); //teacher
          }
      }
    
  • 서브쿼리

    • 회원 몸무게의 평균을 구해서 평균 이상인 회원을 조회 해야 할 경우
      SQL : select m from Member m where weight >= (select AVG(subM.weight) from Member subM )
      private static void findMember(EntityManager em, String name) {
          CriteriaBuilder cb = em.getCriteriaBuilder();	//빌더 선언
          CriteriaQuery<Member> cq = cb.createQuery(Member.class); 	//생성, 반환 타입 지정
            
          Subquery<Double> subQuery = cq.subquery(Double.class);	//서브쿼리 생성
          Root<Member> subM = subQuery.from(Member.class);
          subQuery.select(cb.avg(subM.get("memberInbody").get("weight")));
    
          Root<Member> m = cq.from(Member.class);	//메인쿼리 생성
          cq.select(m).where(cb.ge(m.get("memberInbody").get("weight"), subQuery));
          List<Member> query = em.createQuery(cq).getResultList();
    
          for (Member member : query) {
              System.out.println(member.getName());
          }
      }
    
  • IN 조건

      private static void findMember(EntityManager em, String name) {
          CriteriaBuilder cb = em.getCriteriaBuilder();	//빌더 선언
          CriteriaQuery<Member> cq = cb.createQuery(Member.class); 	//생성, 반환 타입 지정
            
          Root<Member> m = cq.from(Member.class);	//메인쿼리 생성
      
          cq.select(m).where(cb.in(m.get("name"))   //in 조건 생성
          		.value("홍길동")
          		.value("김철수")
          );
            
          List<Member> query = em.createQuery(cq).getResultList();
    
          for (Member member : query) {
              System.out.println(member.getName());
          }
      }
    



마무리

오늘은 JPQL에 대해서 알아 보았습니다. 정리한 내용 외에도 쿼리를 작성 하는 방법에는 수많은 옵션과 조건이 있으므로
여러 가지 유형의 쿼리를 가지고 작성 하는 방법을 연습해서 익숙 하게 작성 할 수 있도록 알아두는 것이 좋을 것입니다.





#JPA #JPQL #hibernate #Spring #SpringBoot #Developer #JAVA #BackEnd #FrontEnd

Back to blog