jpa는 다양한 쿼리 방법을 지원한다
1. jpql
2. jpa criteria
3. queryDSL
4. 네이티브 SQL
5. JDBC API 직접 사용, MyBatis, SpringJdbcTemplate함께 사용
jpql = 동적 쿼리 힘들다.
실무에서 문자열 +힘들다
criteria = sql 스럽지 않다, 유지보수 어렵다.
JPQL
- JPQL은 객체지향 쿼리 언어다. 따라서 테이블을 대상으로 쿼리를 작성하는 것이 아니라 엔티티 객체를 대상으로 쿼리를 작성한다.
- JPQL은 SQL을 추상화해서 특정데이터베이스 SQL에 의존하지 않는다.
- JPQL은 결국 SQL로 변환된다.(매핑 정보와 방언을 조합해서)
JPQL 문법
- 엔티티와 속성은 대소문자 구분한다.(Member)
- JPQL 키워드는 대소문자 구분을 하지 않는다 (select, from, where)
- 엔티티 이름을 사용한다. 테이블이름이 아니다
- 별칭은 필수적으로 사용해야 한다. (Member m) as 생략가능
- 집합과 정렬을 사용가능하다 (COUNT, SUM, AVG, MAX, MIN, GROUP BY, HAVING, ORDER BY)
TypeQuery vs Query
TypeQuery : 반환 타입이 명확할 때 사용한다.
Query : 반환 타입이 명확하지 않을 때 사용한다.
TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
Query query2 = em.createQuery("select m.username, m.age from Member m");
query1의 경우 Member.class로 타입이 명확하다 하지만 query2의 경우 username은 String, age는 int이므로 타입이 명확하지 않아 TypedQuery를 사용하지 못한다. 만약 username만 select 하는 경우에는 String으로 TypedQuery를 사용할 수 있다.
결과 조회 API
- query.getResultList(): 결과가 하나 이상일 때, 리스트 반환
- 결과가 없으면 빈 리스트를 반환한다.
TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
List<Member> resultList = query1.getResultList();
for (Member member1 : resultList) {
System.out.println("member1 = " + member1);
}
- query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환
- 결과가 없으면 : javax.persistence.NoResultException
- 둘 이상이면 : javax.persistence.NonUniqueResultException
TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class);
Member singleResult = query1.getSingleResult();
System.out.println("singleResult = " + singleResult);
파라미터 바인딩 - 이름 기준, 위치 기준
이름 기준 파라미터 바인딩을 사용하려면 쿼리에 : 표시를 사용한 후 변수 명을 작성해 주고 setParameter 메서드를 사용하면 된다.
Member singleResult = em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username", "member1")
.getSingleResult();
위치 기반은 ? 표시를 사용한 후 위치를 나타내는 숫자를 적어서 사용 후 serParameter 메서드를 사용해서 값을 할당해 주면 된다.
Member singleResult = em.createQuery("select m from Member m where m.username = ?1", Member.class)
.setParameter(1, "member1")
.getSingleResult();
※ 위치 기준은 대도록 사용하지 않는 것을 권장한다 왜냐하면 쿼리를 수정 시 바인딩을 추가했을 때 뒤에 위치의 순서들이 다 바뀌기 때문에 모두 수정해주어야 하기 때문이다.
프로젝션
- SELECT 절에 조회할 대상을 지정하는 것
- 프로젝션 대상: 엔티티, 임베디드 타입, 스칼라 타입
- SELECT m.team FROM Member m -> 엔티티 프로젝트
- DISTINCT로 중복 제거가능
위 사진과 같이 em.createQuery로 얻은 result의 Member들은 다 영속성 컨텍스트에서 다 관리가 된다.
즉 setUsername을 했을 때 update 쿼리가 나가게 된다.
반환 타입이 명확하지 않을 때
em.createQuery("SELECT m.username, m.age from Member m");
1. 위 쿼리 처럼 반환타입이 명확하지 않을 땐 Object[]을 return 한다.
List resultList = em.createQuery("select m.username, m.age from Member m ").getResultList();
Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
2. MemberDto클래스를 생성해서 MemberDto 클래스의 타입으로 받는다
public class MemberDto {
private String username;
private int age;
List<MemberDto> resultList = em.createQuery("select new org.example.MemberDto(m.username, m.age) from Member m", MemberDto.class)
.getResultList();
MemberDto memberDto = resultList.get(0);
System.out.println("username = " + memberDto.getUsername());
System.out.println("age = " + memberDto.getAge());
MemberDto는 Entity가 아니므로 sql에서 사용하려면 new로 새로 할당해주어야 한다. 이때 패키지명을 다 적어야 한다. (org.example)
이때 MemberDto의 생성자에는 sql에서 사용하는 생성자와 순서, 타입이 일치하는 생성자가 필요하다.
페이징
- JPA는 페이징을 다음 두 API로 추상화한다.
- setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
- setMaxResults(int maxResult) : 조회할 데이터 수
for (int i = 0;i < 100;i++) {
Member member = new Member();
member.setUsername("member1");
member.setAge(i);
em.persist(member);
}
em.flush();
em.clear();
List<Member> resultList = em.createQuery("select m from Member m order by m.age desc", Member.class)
.setFirstResult(0)
.setMaxResults(10)
.getResultList();
System.out.println("result.size() = " + resultList.size());
for(Member member1: resultList) {
System.out.println("member = " + member1);
}
조인
- 내부 조인: SELECT m FROM Member m JOIN m.team t
- 외부 조인: SELECT m FROM Member m LEFT JOIN m.team t
- 세타 조인: SELECT count(m) from Member m, Team t where m.username = t.name
조인 - ON 절
- ON절을 활용한 조인(JPA 2.1부터 지원)
- 조인 대상 필터링
- 연관관계없는 엔티티 외부 조인 (하이버네이트 5.1부터)
- ex)
- SELECET m, t FROM Member m LEFT JOIN m.team t ON t.name = 'A'
- SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name(연관관계 x)
서브 쿼리
서브 쿼리는 쿼리 안에 쿼리가 들어가는 쿼리이다
ex) 한 건이라도 주문한 고객
selext m from Member m
where (select count(o) from Order o where m = o.member) > 0
서브 쿼리 지원 함수
- [NOT] EXISTS : 서브쿼리에 결과가 존재하면 참
- ALL/ANY/SOME
- [NOT] IN : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
- ex) select o from Order o where o.orderAmount > ALL (select p.stockAmount from Product p)
JPA 서브 쿼리 한계
- JPA는 WHERE, HAVING 절에서만 서브 쿼리를 사용할 수 있다.
- 하지만 하이버네이트에서는 SELECT 절도 지원한다.
- FROM 절의 서브 쿼리는 현재 JPQL에서 불가능하다.
- 조인으로 풀 수 있으면 풀어서 해결
- 안되면 sql 두 번 날린다 or native sql을 사용 or service로직에서 처리한다.
- hibernate 6부터는 from절의 서브 쿼리도 지원한다.
JPQL 타입 표현
- 문자 : 'Hello'
- 숫자 : 10L, 10D, 10F
- Boolean : TRUE, FALSE
- ENUM : jpabook.Member.Type.Admin (패키지명 포함)
- 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용)
조건식 - case 식
- 기본 case 식
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
- 단순 case 식
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
end
from Team t
- COALESCE: 하나씩 조회해서 null이 아니면 반환한다.
Member member1 = new Member();
member1.setUsername("member1");
member1.setAge(10);
Member member2 = new Member();
member2.setUsername(null);
member2.setAge(10);
em.persist(member1);
em.persist(member2);
em.flush();
em.clear();
List<String> resultList = em.createQuery("select coalesce(m.username, '이름 없는 회원') from Member m ", String.class).getResultList();
for(String name: resultList) {
System.out.println("member = " + name);
}
- NULLIF : 두 값이 같으면 null 반환, 다르면 첫 번째 값 반환
JPQL 기본 함수
JPQL이 제공하는 기본 함수는 데이터베이스에 상관없이 쓸 수 있다.
- CONCAT : 문자열 두 개를 더하는 함수이다.
- "select concat('a', 'b') from Member m" -> 출력 ab
- SUBSTRING : 문자열을 자른다.
- "select substring(m.username, 2, 3) From Member m"
- TRIM : 공백 제거
- LOWER, UPPER : 대소문자 변환
- LENGTH : 문자열 길이
- LOCATE : 문자열의 위치를 찾는다.
- "select locate('de', 'abcdegf') From Member m" -> 출력 4
- ABS, SQRT, MOD : 수학 함수
- SIZE, INDEX
- "select size(t.members) From Team t" -> 컬렉션 크기를 출력해 준다.
'JPA' 카테고리의 다른 글
@NoargsConstructor(AccessLevel.PROTECTED) (0) | 2024.06.19 |
---|---|
JPA MultipleBagFetchException에러 해결하기 (0) | 2024.01.20 |
값 타입, 값 타입 컬렉션 (0) | 2023.09.01 |
임베디드 타입 (0) | 2023.08.31 |
기본값 타입 (0) | 2023.08.31 |