소개
- EntityManager.find → 이후 필요시 객체 그래프 탐색
- 검색을 하는 경우 엔티티 객체를 대상으로 쿼리를 작성해야한다.
- 모든 DB 데이터를 객체로 변환하는 것은 좋지 않기 때문에 필요한 데이터만 불러오기 위해 검색 조건이 포함된 SQL을 사용한다.
- JPQL은 객체 지향 쿼리 언어이고 엔티티 객체를 대상으로 쿼리를 날린다. (SQL은 DB 대상 쿼리)
- SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않는다.
List<Member> members = em.createQuery(
"select m from Member m where m.username like '%kim%'",
Member.class
).getResultList();
Criteria
- JPQL은 동적 쿼리를 만드는 것이 어렵다. 또한 문자열 형태이기 때문에 쿼리를 작성하고 동적 쿼리를 작성할 때 문자열을 이어붙히는 부분에서 버그가 많이 발생할 수 있다.
- 자바 표준에서 지원 스펙이다.
- 코드로 쿼리를 작성하는 형태이기 때문에 잘 못 작성한 경우 컴파일 단계에서 에러를 찾을 수 있고 동적 쿼리를 자바 작성하는 것이 편리하다.
- 유지보수 측면에서 좋지않아(복잡하고 실용성 X) 실무에서 권장되지 않는다.
- QueryDSL을 사용한다!!
QueryDSL
- 자바 코드로 JPQL을 작성할 수 있다.
- 컴파일 단계에서 에러를 찾을 수 있고 동적 쿼리를 작성하기 편리하다.
- 단순하고 쉽게 이용할 수 있어 실무에서 권장된다.
네이티브 SQL
- SQL을 직접 사용하는 기능
- JQPL로 해결할 수 없는 특정 DB에 의존적인 기능 사용
- NativeQuery는 flush 및 commit을 하고 DB에서 조회
em.createNativeQuery("select usernmae from member"), Member.class).getResultList();
- JPA를 이용하면서 JDBC 커넥션, JdbcTemplate, MyBatis 등과 함께 사용가능하다.
- JPA와 관련이 없는 기술 스펙이기 때문에 영속성 컨텍스트를 적절한 시점에 강제로 flush 해야한다.
JPQL 문법
기본 문법 & 쿼리 API
- 엔티티와 속성은 대소문자를 구분한다. (객체의 엔티티 명과 필드 명을 반드시 동일하게 사용한다.)
- JPQL 키워드는 대소문자를 구분하지 않는다.
- @Entity의 name 프로퍼티를 이용해서 사용한다.
- 별칭을 반드시 지정한다.
// Member : 엔티티 이름, username : 필드명, m : 별칭
select m from Member m where m.username = 'zayson'
count, sum, avg, max, min, group by, having
과 같은 집계 함수와 정렬(order by)
을 지원한다.- TypeQuery와 Query를 이용한다.
- TypeQuery : 리턴 타입이 명확한 경우 사용
- Query : 리턴 타입이 명확하지 않은 경우 사용
// 회원이라는 타입이 명확하다.
TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
// m.age : int, m.usernmae : String 리턴 타입이 명확하지 않다.
Query query2 = em.createQuery("select m.age, m.username from Member m", Member.class);
- 결과 row가 1개 이상인 경우 getResultList()를 이용할 수 있다.
List<Member> results = query1.getResultList(); // List로 리턴
for (Member result : results) {
// logic
}
- 결과가 반드시 1개인 경우 getSingleResult()를 이용할 수 있다.
Member singleResult = query1.getSingleResult();
🚨 getSingleResult()의 경우 값이 없거나 많은 경우 예외를 발생시킨다.
결과 없는 경우 : NPE
결과 많은 경우 : NonUniqueResult
💡 getResultList()의 경우 결과가 없는 경우에 대해서는 emptyList()를 리턴하기 때문에 NPE에는 안전하다.
- 파라미터 바인딩 시에는
콜론(:) 파라미터 명으로 사용하고, setParameter(파라미터 명, 실제 바인딩할 데이터")를 통해 바인딩한다.
- ?를 이용한 위치 기반 파라미터 바인딩을 지원하지만 권장되지 않는다.
프로젝션
- select 절에서 조회할 대상을 지정하는 것을 의미한다.
- distinct를 이용해 중복 제거가 가능하다.
- 엔티티, 임베디드 타입, 스칼라 타입을 지정할 수 있다.
// m : Entity
select m from Member m
// m.team : 연관관계에 있는 엔티티
select m.team from Member m
// m.address : 임베디드 타입
select m.address from Member m
// m.username, m.age : 스칼라 타입(기본 타입)
select m.username, m.age from Member m
- 엔티티 프로젝션을 하는 경우 로딩된 데이터 모두가 영속성 컨텍스트에서 관리된다.
// 엔티티 프로젝션을 하는 경우 로딩한 데이터들 모두가 영속성 컨텍스트에서 관리된다.
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
// 변경 감지를 통해 update 쿼리가 발생된다.
Member findMember = members.get(0);
findMember.setAge(20);
- 임베디드 타입 프로젝션의 경우 임베디드 타입이 엔티티에 소속되어 있기 때문에 소속된 엔티티를 이용해 조회해아한다.
Address address = em.createQuery("select o.address from Order o", Address.class).getResultList();
- 여러 타입의 데이터를 조회하는 경우 3가지 방식이 존재한다.
- Query를 이용해 조회한다.
List results = em.createQuery("select m.username, m.age from Member m").getResultList();
Object[] result = (Object[]) results.get(0);
// result[0] : username
// result[1] : age
- Object[] 타입으로 조회한다. (TypeQuery)
List<Object[]> results = em.createQuery("select m.username from Member m").getResultList();
Object[] result = results.get(0);
- new 명령어로 조회한다.
- 조회할 데이터를 Dto로 매핑해서 조회한다.
- new 키워드와 함께 패키지 명이 포함된 전체 클래스명으로 선언한다.
- 순서와 타입이 일치하는 생성자가 필요하다.
List<MemberDto> results =
em.createQuery("select new jpql.domain.MemberDto(m.username, m.age)" +
" from Member m", MemberDto.class).getResultList();
페이징
- 벤더에 알맞은 페이징 쿼리를 JPA가 만들어준다.
- setFirstResult(int startPosition) : 조회 시작 위치
- setMaxResults(int maxResult) : 조회할 데이터 수
List<Member> results = em.createQuery("select m from Member m order by m.age asc", Member.class)
.setFirstResult(0) // 첫 번째 데이터 부터
.setMaxResults(10) // 10개를 가져온다.
.getResultList();
조인
- 내부 조인 (inner는 생략이 가능하다.)
String query = "select m from Member m inner join m.team t";
List<Member> results = em.createQuery(query, Member.class).getResultList();
// 쿼리 발생
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
- 외부 조인 (outer는 생략이 가능하다.)
String query = "select m from Member m left join m.team t";
List<Member> results = em.createQuery(query, Member.class).getResultList();
// 쿼리 발생
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
- 세타 조인 : 연관관계가 없는 조인
String query = "select m from Member m, Team t where m.username = t.name";
List<Member> results = em.createQuery(query, Member.class).getResultList();
// 쿼리 발생
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
- ON절을 이용한 조인
- 조인할 대상을 필터링할 수 있다.
// 팀 이름이 A인 팀과 회원을 조인
String query = "select m,t from Member m left join m.team t on t.name = 'A'
- 연관관계 없는 엔티티도 외부 조인을 할 수 있다.
// 회원 이름과 팀 이름이 같은 대상 외부 조인
String query = "select m,t from Member m left join Team t on m.username = t.name
서브쿼리
- SQL의 서브쿼리와 동일하다.
- (Not) Exists, All, Any, Some , In 지원
- JPA 표준 스펙에서는 WHERE, HAVING 절에서만 서브쿼리를 사용할 수 있다.
- Hibernate는 SELECT 절에서도 서브쿼리를 사용할 수 있다.
- FROM절의 서브쿼리는 현재 JPQL에서 불가능하기 때문에 조인으로 풀어서 사용해야 한다.
- 데이터를 로딩하고 애플리케이션 레벨에서 필요한 부분만 사용하거나 쿼리를 두번 날리는 형식으로도 해결 가능
- Native Query를 사용해서 해결하는 것도 가능
타입
- SQL에서 지원하는 문법을 대부분 지원한다.
- 문자 : ‘HELLO’
- 숫자 : 10L(Long), 10D(Double). 10F(Float)
- Boolean : TRUE, FALSE
- ENUM : 패키지명을 전체 포함해서 사용한다.
(jpql.domain.MemberType.ADMIN)
- 엔티티 타입 : TYPE을 이용한다. 상속관계에서 사용한다.
select i from Item i where type(i) = Book
String query = "select m.username, 'JPA', TRUE from Member m where m.type = jpql.domain.MemberType.ADMIN";
조건식
- 기본 CASE식
select
case when m.age <= 10 then '학생'
when m.age >= 60 then '노인'
else '일반'
end
from Member m
- 단순 CASE식
select
case when 'teamA' then '팀A'
when 'teamB' then '팀B'
else '팀C'
end
from Member m
- COALESCE : 처음부터 한개씩 조회해서 null이 아닌 경우를 반환한다.
String query = "select coalesce(m.username, '익명') from Member m";
- NULLIF : 두 값이 같으면 null 반환하고 다른 경우 첫번째 값을 반환한다.
// username이 관리자인 경우 null 반환
String query = "select nullif(m.username, '관리자') from Member m";
기본 함수
- CONCAT (|| 도 가능), SUBSTRING, TRIM, LOWER, UPPER, LENGTH, LOCATE (찾는 문자의 위치)
- ABS, SQRT, MOD,
- SIZE(컬렉션의 크기), INDEX(컬렉션의 위치, 사용 권장 X)
- 위의 함수들은 JPQL이 제공하는 표준함수로써 모든 DB 벤더에서 사용할 수 있다.
- 사용자가 지정한 함수를 사용하고자 할때는 사용할 DB벤더를 지정하고 사용한다.
- 설정 정보에 사용자 정의 Dialect를 지정한다.
📄 References
김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 : https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
반응형
'Backend > JPA' 카테고리의 다른 글
JPQL 중급 (0) | 2022.08.03 |
---|---|
페치 조인 (Fetch Join) (0) | 2022.08.03 |
값 타입 (0) | 2022.07.26 |
영속성 전이 (Cascade), 고아 객체 (0) | 2022.07.21 |
즉시 로딩과 지연 로딩 (0) | 2022.07.21 |