728x90
반응형
객체지향 쿼리 언어
개요
JPA를 사용하면 엔티티 객체를 중심으로 개발을 하기 때문에 검색 쿼리 실행시에도 테이블 대상이 아닌 엔티티 객체를 대상으로 검색을 해야한다. 하지만 모든 데이터베이스 데이터를 객체로 변환해서 검색하는 것은 불가능하며 애플리케이션이 필요한 데이터만 가져오려면 결국 검색 조건이 포함된 SQL을 사용해야 한다.
JPQL
- JPA가 제공하는 SQL을 추상화한 객체 지향 쿼리 언어
- SQL과 문법 유사, ANSI 표준 지원
- SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
- JPQL은 엔티티 객체를 대상으로 쿼리하고 SQL은 데이터베이스 테이블을 대상으로 쿼리한다.
예제
// JpaMain.java
List<Member> result = em.createQuery("select m From Member m where m.username like '%kim%'", Member.class)
.getResultList();
for (Member member : result) {
System.out.println("member = " + member);
}
Criteria
- 문자(String)가 아닌 자바코드로 JPQL을 작성할 수 있다.
- JPQL 빌더 역할, JPA 공식 기능
- 너무 복잡하고 실용성이 없는 것이 단점이어서 실무에서 사용하지 않는다.
- Criteria 대신 QueryDSL 사용 권장
예제
// JpaMain.java
// Criteria 사용 준비
CriteriaBuilder cb = em.getCriteriaBuilder();
// 쿼리 생성
CriteriaQuery<Member> query = cb.createQuery(Member.class);
// from
Root<Member> m = query.from(Member.class);
// select + from + where
CriteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("username"), "kim"));
List<Member> result = em.createQuery(cq).getResultList();
QueryDSL
- 문자(String)가 아닌 자바코드로 JPQL을 작성할 수 있다.
- JPQL 빌더 역할
- 컴파일 시점에 문법 오류를 찾을 수 있다.
- 동적쿼리 작성에 편리하며 단순하고 쉽기 때문에 실무 사용을 권장한다.
예제
// JpaMain.java
// QueryDSL은 기본 설정이 필요하다.
JPAFactoryQuery query = new JPAQueryFactory(em);
QMember m = QMember.member;
List<Member> list = query.selectFrom(m).where(m.age.gt(18)).orderBy(m.name.desc()).fetch();
※ QueryDSL은 기본적인 설정이 필요하여 추후에 다룰 예정
Native SQL
- JPA가 제공하는 SQL을 직접 사용하는 기능으로 JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능 사용이 가능하다.
- 예) Oracle CONNECT BY, 특정 데이터베이스만 사용하는 SQL 힌트(하이버네이트도 방언에 셋팅하여 지원)
예제
// JpaMain.java
em.createNativeQuery("select MEMBER_ID, city, street, zipcode, USERNAME from MEMBER").getResultList();
JDBC 직접 사용, SpringJdbcTemplate 등
- JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나 SpringJdbcTemplate, MyBatis 등을 함께 사용 가능하다.
- 단, 영속성 컨텍스트를 적절한 시점에 강제로
flush()
해야 한다. (ex. JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트를 수동 플러시)
JPQL (Java Persistence Query Language)
소개
- JPQL은 객체지향 쿼리 언어로 데이터베이스 테이블 대상이 아닌 엔티티 객체를 대상으로 쿼리한다.
- JPQL은 SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
- JPQL은 결국 SQL로 변환된다.
문법
- 엔티티와 속성은 대/소문자를 구분한다.
- JPQL 키워드는 대/소문자를 구분하지 않는다. (SELECT, FROM, WHERE)
- 테이블 이름이 아닌 엔티티명을 사용한다.
- 별칭은 필수로 사용한다. (AS는 생략 가능)
- GROUP BY, HAVING, 그룹함수(COUNT, SUM, AVG, MAX, MIN), ORDER BY 사용 가능하다.
TypeQuery, Query
- TypeQuery : 반환 타입이 명확할 때 사용한다.
- Query : 반환 타입이 명확하지 않을 때 사용한다.
예제
// JpaMain.java
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
// 1. TypeQuery
TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
// 2. Query
Query query2 = em.createQuery("select m from Member m");
결과조회 API
getResultList()
: 결과가 하나 이상일 때 사용하며 리스트를 반환한다. (결과가 없으면 빈 리스트 반환)getSingleResult()
: 결과가 정확히 하나일 때 사용하며 단일 객체를 반환한다.
(결과가 없으면javax.persistence.NoResultException
, 둘 이상이면javax.persistence.NonUniqueResultException
예외를 발생)
getResultList() 예제
// JpaMain.java
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
List<Member> result = query1.getResultList();
for(Member m : result) {
System.out.println("member = " + m);
}
getSingleResult() 예제
// JpaMain.java
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
TypedQuery<Member> query1 = em.createQuery("select m from Member m where m.age = 10", Member.class);
Member result = query1.getSingleResult();
System.out.println("member = " + result);
getSingleResult() 결과가 없을 경우
// JpaMain.java
TypedQuery<Member> query1 = em.createQuery("select m from Member m where m.age = 10", Member.class);
Member result = query1.getSingleResult();
System.out.println("member = " + result);
getSingleResult() 결과가 2개 이상일 경우
// JpaMain.java
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
// getSingleResult() 결과 2개 이상
Member member2 = new Member();
member2.setUsername("member2");
member2.setAge(10);
em.persist(member2);
TypedQuery<Member> query1 = em.createQuery("select m from Member m where m.age = 10", Member.class);
Member result = query1.getSingleResult();
System.out.println("member = " + result);
파라미터 바인딩
위치 기반 바인딩은 잘 사용하지 않는다. 왜냐하면 중간에 파라미터 추가 시 뒤에 순서가 다 밀리고 이로 인해 장애가 발생할 수 있다.
예제
// JpaMain.java
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
// 1. 이름 기반
Member result = em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();
System.out.println("result.username : " + result.getUsername());
// 2. 위치 기반
Member result = em.createQuery("select m from Member m where m.username = ?1", Member.class)
.setParameter(1, "member1")
.getSingleResult();
System.out.println("result.username : " + result.getUsername());
프로젝션
- SELECT 절에 조회할 대상을 지정하는 것
- 프로젝션 대상으로는 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터 타입)이 있다.
- DISTINCT 사용하여 중복 제거 가능하다.
엔티티 프로젝션
// 엔티티 프로젝션은 결과가 다 영속성 컨텍스트에서 관리된다.
List<Member> result = em.createQuery("select m from Member m", Member.class).getResultList();
// 영속성 컨텍스트로 관리되기 때문에 나이값 변경 update 쿼리 실행
Member findMember = result.get(0);
findMember.setAge(20);
엔티티 프로젝션 조인
묵시적 조인
List<Team> result = em.createQuery("select m.team from Member m", Team.class).getResultList();
명시적 조인
List<Team> result = em.createQuery("select t from Member m join m.team t", Team.class).getResultList();
임베디드 타입 프로젝션
em.createQuery("select o.address from Order o", Address.class).getResultList();
스칼라 타입 프로젝션
em.createQuery("select distinct m.username, m.age from Member m").getResultList();
여러 값 조회
Query 타입으로 조회
List resultList = em.createQuery("select m.username, m.age from Member m").getResultList();
// 결과를 Object[] 로 캐스팅해서 사용
Object obj = resultList.get(0);
Object[] objects = (Object[]) obj;
System.out.println("query.username = " + objects[0]);
System.out.println("query.age = " + objects[1]);
Object[] 타입으로 조회
List<Object[]> resultList = em.createQuery("select m.username, m.age from Member m").getResultList();
Object[] obj = resultList.get(0);
System.out.println("object.username = " + obj[0]);
System.out.println("object.age = " + obj[1]);
new 키워드로 조회
- 단순값을 DTO로 바로 조회
- 패키지명을 포함한 전체 클래스명 입력
- 순서와 타입이 일치하는 생성자가 필요
public class MemberDTO {
private String username;
private int age;
public MemberDTO(){}
public MemberDTO(String username, int age) {
this.username = username;
this.age = age;
}
// getter, setter
}
// 패키지명을 포함한 전체 클래스명을 입력해야 하고, 순서가 일치하는 생성자가 있어야 한다.
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("new.username = " + memberDTO.getUsername());
System.out.println("new.age = " + memberDTO.getAge());
페이징
- JPA는 페이징을 다음 두 API로 추상화
setFirstResult(int startPosition)
: 조회 시작 위치 (0부터 시작)setMaxResults(int maxResult)
: 조회할 데이터 수 - 데이터베이스 방언에 따라 페이징 쿼리 실행 (H2 DB가 Oracle, MSSQL 스타일 쿼리를 에뮬레이팅해서 실행)
List<Member> result = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
System.out.println("result.size : " + result.size());
// toString() 이용 출력
for(Member m : result) {
System.out.println("member : " + m);
}
데이터베이스 방언 별 페이징 쿼리
728x90
반응형
'Dev > JPA' 카테고리의 다른 글
[JPA] 객체지향 쿼리 언어 - 중급문법 (1) (0) | 2021.09.10 |
---|---|
[JPA] 객체지향 쿼리 언어 - 기본문법 (2) (0) | 2021.09.08 |
[JPA] 실습 - 값 타입 매핑 (0) | 2021.09.02 |
[JPA] 값 타입 (0) | 2021.09.01 |
[JPA] 실습 - 연관관계 관리 (0) | 2021.08.27 |
댓글