Search
Duplicate

[Spring JPA] 객체지향 쿼리 언어1 - 기본 문법

날짜
2022/04/29 05:32
상태
스프링 JPA 기본
속성 1
Spring
JAVA
JPA
JPQL
담당자

JPQL 소개

JPQL은 객체지향 쿼리 언어다. 따라서 테이블을 대상으로 쿼리하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
JPQL은 SQL을 추상화해서 특정데이터베이스 SQL에 의존하지 않는다.
JPQL은 결국 SQL로 반환된다.

JPQL 문법

select m from Member as m where m.age > 18
엔티티와 속성은 대소문자 구분 해야된다. (Member, age)
JPQL 키워드는 대소문자 구분이 필요 없다. (SELECT, FROM, where)
엔티티 이름 사용, 테이블 이름 아님 (Member)
별칭은 필수(m)(as는 생략가능)

TypeQuery, Query

TypeQuery: 반환 타입이 명확할 때 사용
Query: 반환 타입이 명확하지 않을 때 사용
TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class); TypedQuery<String> query2 = em.createQuery("select m.username from Member m", String.class); Query query3 = em.createQuery("select m.username, m.age from Member m");
Java
복사

결과 조회 API

query.getResultList(): 결과가 하나 이상일 때, 리스트 반환
결과가 없으면 빈 리스트 반환
query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환
결과가 없으면: javax.persistence.NoResultException
둘 이상이면: javax.persistence.NonUniqueResultException
TypedQuery<Member> query1 = em.createQuery("select m from Member m", Member.class); List<Member> resultList = query1.getResultList(); for (Member member1 : resultList) { System.out.println("id >>> " + member1.getUsername()); }
Java
복사
TypedQuery<String> query2 = em.createQuery("select m from Member m where m.id = 10", String.class); Member singleResult = query1.getSingleResult(); System.out.println("id : " + singleResult.getId());
Java
복사

파라미터 바인딩 - 이름 기준, 위치 기준

Member member = new Member(); member.setUsername("member1"); em.persist(member); Member memberResult = em.createQuery("select m from Member m where m.username = :username", Member.class) .setParameter("username", "member1") .getSingleResult(); System.out.println("SingleResult = " + memberResult.getUsername());
Java
복사

프로젝션

SELECT 절에 조회할 대상을 지정하는 것
프로젝션 대상 : 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자등 기본 데이터 타입)
SELECT m FROM Member m : 엔티티 프로젝션
SELECT m.team FROM Member m : 엔티티 프로젝션
SELECT m.address FROM Member m : 임베디드 타입 프로젝션
SELECT m.username, m.age FROM Member m : 스칼라 타입 프로젝션
DISTINCT로 중복 제거

프로젝션 - 여러 값 조회

SELECT m.username, m.age FROM Member m
1. Query 타입으로 조회
2. Object[] 타입으로 조회
3. new 명령어로 조회
단순 값을 DTO로 바로 조회
SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
패키지 명을 포함한 전체 클래스 명 입력
순서와 타입이 일치하는 생성자 필요
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(memberDTO.getUsername()); System.out.println(memberDTO.getAge());
Java
복사
package jpql; public class MemberDTO { private String username; private int age; public MemberDTO(String username, int age) { this.username = username; this.age = age; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
Java
복사

페이징 API

JPA는 페이징을 다음 두 API로 추상화
setFirestResult(int startPosition) : 조회 시작 위치 (0부터 시작)
setMaxResults(int maxResult) : 조회할 데이터 수

페이징 API 예시

for( int i = 0; i < 100; i++) { Member member = new Member(); member.setUsername("member" + i); 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(resultList.size()); for (Member member1 : resultList) { System.out.println("member = " + member1); } tx.commit();
Java
복사

조인

내부 조인 : SELECT m FROM Member m [INNER] JOIN m.team t
외부 조인 : SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
세타 조인 : select count(m) from Member m, Team t where m.username = t.name

조인 - ON 절

ON절을 활용한 조인(JPA 2.1부터 지원)
1. 조인 대상 필터링
2. 연관관계 없는 엔티티 외부 조인(하이버네이트 5.1 부터)
1. 조인 대상 필터링
회원과 팀을 조인하면서, 팀 이름이 A인 팀만 조인
JPQL : SELECT m , t FROM Member m LEFT JOIN m.team t on t.name = 'A'
SQL
복사
SQL : SELECT m.* , t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID=t.id and t.name = 'A'
SQL
복사
2. 연관관계 없는 엔티티 외부 조인
예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
JPQL : SELECT m , t FROM Member m LEFT JOIN m.team t on m.username = t.name
SQL
복사
SQL : SELECT m.* , t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
SQL
복사

서브 쿼리

나이가 평균보다 많은 회원
JPQL : select m from Member m where m.age > ( select avg(m2.age) from Member m2 )
SQL
복사
한 건이라도 주문한 고객
SQL : select m from Member m where (select count(o) from Order o where m = o.member) > 0
SQL
복사

서브 쿼리 지원 함수

[NOT] EXISTS (subquery) : 서브쿼리에 결과가 존재하면 참
{ALL | ANY | SOME} (subquery)
ALL 모두 만족하면 참
ANY, SOME : 같은 의미, 조건을 하나라도 만족하면 참
[NOT] IN (subquery) : 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
서브 쿼리 - 예제
팀A 소속인 회원
select m from Member m where exists ( select t from m.team t where t.name = '팀')
SQL
복사
전체 상품 각각의 재고보다 주문량이 많은 주문들
select o from Order o where o.orderAmout > ALL ( select p.stockAmount from Product p)
SQL
복사
어떤 팀이든 팀에 소속된 회원
select m from Member m where m.team = ANY ( select t from Team t )
SQL
복사

JPA 서브 쿼리 한계

JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능
SELECT 절도 가능(하이버네이트에서 지원)
FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
조인으로 풀 수 있으면 풀어서 해결
쿼리를 두번 날려서 해결
네이티브로 넘겨서 해결

JPQL 타입 표현

문자: 'HELLO', 'She''s'
숫자: 10L(Long), 10D(Double), 10F(Float)
Boolean: TRUE, FALSE
ENUM: jpabook.MemberType.Admin
try { Team team = new Team(); team.setName("TeamA"); em.persist(team); Member member = new Member(); member.setUsername("TeamA"); member.setType(MemberType.ADMIN); member.setAge(10); member.setTeam(team); em.persist(member); em.flush(); em.clear(); String query = "select m.username, 'HELLO', true" + " From Member m " + "where m.type = jpql.MemberType.USER"; List<Object[]> resultList = em.createQuery(query) .getResultList(); System.out.println(resultList.size()); for (Object[] objects : resultList) { System.out.println(objects[0]); System.out.println(objects[1]); System.out.println(objects[2]); }
Java
복사

조건식 - CASE 식

기본 CASE 식
select case when m.age <= 10 then '학생요금' when m.age >= 60 then '경로요금' else '일반요금' end from Member m
SQL
복사
단순 CASE 식
select case t.name when '팀A' then '인센티브110%' when '팀B' then '인센티브120%' else '인센티브105%' end from Team t
SQL
복사
COALESCE: 하니씩 조회해서 null이 아니면 반환
NULLIF: 두 값이 같이면 null 반환, 다르면 첫번째 값 반환
사용자 이름이 없으면 이름 없는 회원을 반환
select coalesce(m.username, '이름 없는 회원') from Member m
SQL
복사
사용자 이름이 '관리자'면 null을 반환하고 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m
SQL
복사
JPQL 기본 함수
CONCAT
SUBSTRING
TRIM
LOWER, UPPER
LENGTH
LOCATE
ABS, SQRT, MOD
SIZE, INDEX (JPA 용도)
사용자 정의 함수 호출
하이버네이트 사용전 방언에 추가해야한다.
사용하는 DB방언을 상속받고, 사용자 정의 함수를 등록한다.
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"> <persistence-unit name="hello"> <properties> <!-- 필수 속성 --><property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/> <property name="javax.persistence.jdbc.user" value="sa"/> <property name="javax.persistence.jdbc.password" value=""/> <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/> <property name="hibernate.dialect" value="dialect.MyH2Dialect"/>
XML
복사
package dialect; import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.function.StandardSQLFunction; import org.hibernate.type.StandardBasicTypes; public class MyH2Dialect extends H2Dialect { public MyH2Dialect() { registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING)); } }
Java
복사
String query = "select function('group_concat', m.username) FROM Member m"; List<String> resultList = em.createQuery(query, String.class) .getResultList();
Java
복사
이 글은 인프런의
제목 : 자바 ORM 표준 JPA 프로그래밍 - 기본 편
강사 : 김영한 님의 동영상을 참조해 만들었습니다.