JPA JPQL 작성의 모든 것!!
By on November 8, 2019
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