Spring/[인프런 김영한 자바 ORM 표준 JPA 프로그래밍 - 기본편]
[인프런 김영한 자바 ORM 표준 JPA 프로그래밍 - 기본편] 객체지향 쿼리 언어 - 기본 문법
h2boom
2024. 8. 28. 00:22
객체지향 쿼리 언어
- JPA에서 지원하는 다양한 쿼리 방법
- JPQL
- JPA Criteria
- QueryDSL
- 네이티브 SQL
- JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용
JPQL (Java Persistence Query Language)
// JPQL 사용 예시
List<Member> result = em.createQuery(
"select m from Member m where m.name like '%kim%'"
, Member.class).getResultList();
for (Member member : result) {
System.out.println("member = " + member);
}
- JPA를 사용하면 엔티티 객체를 중심으로 개발하고 검색한다.
- 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능하다.
- 필요한 데이터만 DB에서 불러오려면 검색 조건이 포함된 SQL이 필요하다.
- JPQL : SQL을 추상화한 객체지향 쿼리 언어 (= 객체지향 SQL)
- SQL은 데이터베이스 테이블을 대상으로 쿼리, JPQL은 엔티티 객체를 대상으로 쿼리
- JPQL은 특정 데이터베이스 SQL에 의존하지 않는다.
- JPQL은 단순 문자열이기에 동적 쿼리를 작성하기에 어려움이 있다.
- JPQL 문법
- select 문 => select 절 from 절 [where 절] [groupby 절] [having 절] [orderby 절]
- update 문 => update 절 [where 절]
- delete 문 => delete 절 [where 절]
- 엔티티와 속성은 대소문자를 구분하고 JPQL 키워드는 대소문자를 구분하지 않는다.
- 테이블 이름이 아닌 엔티티 이름을 사용한다.
- 별칭은 필수 (as 생략 가능) ex) select m from Member m
- 대부분 SQL과 문법이 동일하다.
- TypeQuery : 반환 타입이 명확할 때 사용
- ex) em.createQuery(select m from Member m, Member.class);
반환 타입이 Member로 명확하다.
- ex) em.createQuery(select m from Member m, Member.class);
- Query : 반환 타입이 명확하지 않을 때 사용
- ex) em.createQuery(select m.username, m.id from Member m);
반환 타입이 String과 Long이기에 명확하지 않다.
- ex) em.createQuery(select m.username, m.id from Member m);
- 결과 조회 메소드
- query.getResultList() : 결과가 하나 이상일 때 리스트 반환
- 결과가 없는 경우 빈 리스트를 반환한다.
- query.getSingleResult() : 결과가 정확히 하나일 때 단일 객체 반환
- 결과가 없으면 NoResultException 예외 발생
- 결과가 둘 이상이면 NonUniqueResultException 예외 발생
- query.getResultList() : 결과가 하나 이상일 때 리스트 반환
// 파라미터 바인딩 예시
// 이름 기준
em.createQuery("select m from Member m where m.username = :username", Member.class)
.setParameter("username",usernameParam);
.getSingleResult();
// 위치 기준
em.createQuery("select m from Member m where m.username = ?1", Member.class)
.setParameter(1,usernameParam);
.getSingleResult();
- 파라미터 바인딩
- 이름 기준
- 위치 기준
- 위치 기준 파라미터 바인딩은 위치가 바뀌는 경우 장애로 이어지기에 사용하지 않는 것이 좋다.
//Query 타입으로 조회
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("result[0] = " + result[0]);
//Object[] 타입으로 조회
List<Object[]> resultList = em.createQuery("select m.username,m.age from Member m")
.getResultList();
Object[] result = resultList.get(0);
System.out.println("result[0] = " + result[0]);
//DTO로 조회
List<MemberDTO> result = em.createQuery("select new org.example.jpql.MemberDTO(m.username,m.age)
from Member m ", MemberDTO.class).getResultList();
MemberDTO memberDTO = result.get(0);
System.out.println("memberDTO = " + memberDTO.getUsername());
- 프로젝션 : SELECT 절에 조회할 대상을 지정하는 것
- 프로젝션 대상 - 엔티티, 임베디드 타입, 스칼라 타입 (숫자, 문자등 기본 데이터 타입)
- 여러 값 조회 방법 ex) SELECT m.username, m.age FROM Member m
- Query 타입으로 조회
- Object[] 타입으로 조회
- new 명령어로 조회 -> 단순 값을 DTO로 바로 조회
- 엔티티로 등록된 클래스가 아닌 경우 select 다음 패키지명을 포함한 전체 클래스명을 입력해줘야 한다.
- 순서와 타입이 일치하는 생성자가 필요하다.
List<Member> result = em.createQuery("select m from Member m order by m.age desc ", Member.class)
.setFirstResult(0)
.setMaxResults(10)
.getResultList();
- 페이징 : 데이터베이스에서 데이터를 읽어와서 화면에 출력할 때 한 번에 모든 데이터를 가져오기보다는 출력될 페이지의 데이터만 나눠서 가져오는 것을 의미한다.
- 페이징 API
- setFirstResult(int startPosition) : 조회 시작 위치
- setMaxResults(int maxResult) : 조회할 데이터 수
- 조인
- 내부 조인
- ex) select m from Member m [INNER] JOIN m.team t
- ex) select m from Member m [INNER] JOIN m.team t
- 외부 조인
- ex) select m from Member m LEFT [OUTER] JOIN m.team t
- 세타 조인
- ex) select count(m) from Member m, Team t where m.username = t.name
- 내부 조인
- 조인 - ON절 활용
- 조인 대상 필터링
- ex) 회원 - 팀 조인 (팀 이름이 A인 팀만 조인)
JPQL => SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'A'
SQL => SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID = t.id and t.name = 'A'
- ex) 회원 - 팀 조인 (팀 이름이 A인 팀만 조인)
- 연관관계 없는 엔티티 외부 조인 (이전에는 내부 조인만 가능)
- ex) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
JPQL => SELECT m, t FROM Member m LEFT JOIN m.username = t.name
SQL => SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
- ex) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
- 조인 대상 필터링
- 서브 쿼리
- ex) 나이가 평균보다 많은 회원
select m form Member m where m.age > (select avg(m2.age) from Member m2) - JPA에서는 SELECT, WHERE, HAVING 절에서만 서브 쿼리 사용 가능
- FROM 절의 서브 쿼리는 불가능하다.
- ex) 나이가 평균보다 많은 회원
- 서브 쿼리 지원 함수
- [NOT] EXISTS (서브쿼리) : 서브 쿼리에 결과가 존재하면 참
- {ALL / ANY / SOME} (서브쿼리) 로 사용할 수 있으며 조건과 비교해서 참인지 아닌지 결정
- ALL : 모두 만족하면 참
- ANY, SOME : 조건을 하나라도 만족하면 참
- [NOT] IN (서브쿼리): 서브 쿼리의 결과 중 하나라도 같은 것이 있으면 참
- [NOT] EXISTS (서브쿼리) : 서브 쿼리에 결과가 존재하면 참
- JPQL 타입 표현
- 문자 - 'HELLO', 'She''s'
- 숫자 - 10L, 10D, 10F
- Boolean - FALSE, TRUE
- ENUM - 패키지를 포함한 전체 클래스명과 ENUM 변수를 적어줘야한다. ex) com.jpa.MemberType.ADMIN
- 엔티티 타입 - ex) select i from Item i where type(i) = Book
//기본 CASE 식
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
//단순 CASE 식
select
case when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t
//COALESCE => 사용자 이름이 없으면 이름없는 회원을 반환
select coalesce(m.username, '이름 없는 회원') from Member m
//NULLIF => 사용자 이름이 관리자면 null을 반환하고 나머지는 본인 이름을 반환
select NULLIF(m.username, '관리자') from Member m
- 조건식 - CASE 식
- 기본 CASE 식
- 단순 CASE 식
- 기본 CASE 식
- COALESCE : 하나씩 조회해서 null이 아니면 반환
- NULLIF : 두 값이 같으면 null 반환, 다르면 첫 번째 값 반환
- JPQL 기본 함수
- CONCAT
- SUBSTRING
- TRIM
- LOWER, UPPER
- LENGTH
- LOCATE
- ABS, SQRT, MOD
- SIZE, INDEX
JPA Criteria
//Criteria 사용 예시
CriteriaBuilder cb = em.getCriteriaBuilder(); //Criteria 사용 준비
CriteriaQuery<Member> query = cb.createQuery(Member.class);
Root<Member> m = query.from(Member.class);
CriteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("name"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList();
- JPA Criteria 특징
- 자바 코드로 JPQL을 작성할 수 있다.
- 오타가 생겨도 컴파일 오류 발생
- 동적 쿼리를 작성할 때 편하다
- SQL 같지 않기에 표준 스펙임에도 잘 사용하지 않는다.
- 코드가 복잡하고 유지보수가 어렵다.
- 자바 코드로 JPQL을 작성할 수 있다.
- Criteria 대신 QueryDSL 사용 권장
QueryDSL
// QueryDSL 사용 예시
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
queryFactory
.select(order)
.from(order)
.join(member, order.memeber)
.where(...)
.fetch();
- QueryDSL 특징
- 문자가 아닌 자바코드로 JPQL 작성 가능
- 컴파일 시점에 문법적인 오류를 찾을 수 있다.
- 동적 쿼리를 작성하기에 편리하다.
- 단순하고 쉽기에 실무 사용 권장
네이티브 SQL
//네이티브 SQL
em.createNativeQuery("select MEMBER_ID, city, street, zipcode, USERNAME from MEMBER")
.getResultList();
- 네이티브 SQL : JPA가 제공하는 SQL을 직접 사용하는 기능
- JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능을 사용하기 위해서 사용한다.
ex) 오라클 CONNECT BY ...
- JPQL로 해결할 수 없는 특정 데이터베이스에 의존적인 기능을 사용하기 위해서 사용한다.
JDBC, SpringJdbcTemplate
- JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나 SpringJdbcTemplate, MyBatis등을 함께 사용 가능하다.
- 이 경우 영속성 컨텍스트를 적절한 시점에 강제로 플러시가 필요하다.
- commit, query 시점에 flush가 되기에 다른 방법에서는 query를 날리는 시점에 flush가 된다.
JDBC는 JPA와 관련이 없기 때문에 flush를 날리지 않기에 직접 flush를 해줘야한다.
- commit, query 시점에 flush가 되기에 다른 방법에서는 query를 날리는 시점에 flush가 된다.
- 이 경우 영속성 컨텍스트를 적절한 시점에 강제로 플러시가 필요하다.
출처: [인프런 김영한 자바 ORM 표준 JPA 프로그래밍 - 기본편]
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 | 김영한 - 인프런
김영한 | JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 실무에서도
www.inflearn.com