*김영한님의 JPA 기본편을 기반으로 작성하였습니다.
JPA는 다양한 쿼리 방식을 지원한다.
JPQL
JPA Criteria
QueryDSL
네이티브 SQL
JDBC API 직접 사용, MyBatis, SpringJdbcTemplate 함께 사용
보통Jpql, QueryDSl로 쿼리를 짜고, 매우 복잡한 극소수의 쿼리들은 SpringJdbcTemplate을 통해 Native SQL로 구현
JPQL이란?
JPQL은 Java Persistence Query Language의 약자로, 객체를 관리하는 Java Persistence API(JPA)의 일부분
JPQL은 객체 지향적인 방식으로 데이터베이스를 조회하고 조작할 수 있도록 해준다.
JPQL은 SQL과 매우 유사하게 생겼지만, 객체를 대상으로 쿼리를 작성하고 실행한다.
또한 JPQL은 JPA에서 제공하는 엔티티 매핑 기능을 이용하여, 엔티티 클래스의 멤버 변수를 테이블의 컬럼으로 매핑하고 이를 바탕으로 쿼리를 작성할 수 있다.
JPQL은 객체 지향적인 쿼리를 작성할 수 있어서, 객체간의 관계나 상속 구조 등을 활용한 복잡한 쿼리도 작성할 수 있고, JPQL은 데이터베이스에 종속적이지 않기 때문에, 다양한 데이터베이스에서 동일한 쿼리를 사용할 수 있다.
JPA로 개발하면 테이블이 아닌 "엔티티 객체" 중심 개발이 되는데, 검색 쿼리가 문제다.
DB 테이블에서 검색을 할 때 역시 엔티티 객체를 대상으로 검색을 하는데, 모든 DB데이터를 객체로 변환하여 검색하는 것은 불가능이다.
따라서 애플리케이션이 필요한 데이터만 DB에서 불러오기 위해 검색 조건이 있는 SQL 필요.
따라서, JPA가 SQL을 추상화한 JPQL을 사용하여 객체를 대상으로 쿼리를 날리는 것.
우리가 JPQL로 객체를 대상으로 한 쿼리를 작성하면, 그것이 SQL로 변환되어 DB에 날라가게 된다.
이때, 데이터베이스에 맞게 쿼리를 날리므로, JPQL를 사용한 쿼리는 특정 데이터베이스에 종속적이지 않다.
JPQL은 SQL과 거의 유사한 문법을 가지지만 "객체" 대상이고, 몇까지의 사소한 문법이 다르다.
JPQL 예시)
en.createQuery("select m From Member m where m.username like '$shy',Member.class);
여기서 볼 수 있듯이, 쿼리가 "String" 으로, 즉 정적으로 나간다. 따라서 동적 쿼리를 생성하는 것이 어려워진다.
ex) 만약 user이름이 null 이면 디폴트 이름으로 USER를 설정하고, null 아니면 그대로
이런 동적 쿼리를 생성하기에 한계가 있다.
이런식으로 Criteria를 통해 동적쿼리를 생성할 수 있지만, 직관적이지 않아 유지보수에 어려움이 있다.
그래서 사용하는 것이 Querydsl
Quertdsl 예시
동적 쿼리 생성 방법
이런식으로 처리 가능하다. 특정 조건을 메소드로 만들어 재사용도 좋고, 가독성도 좋다.
JDBC란?
JDBC는 Java Database Connectivity의 약자로, 자바 언어로 작성된 애플리케이션에서 데이터베이스와 연동하기 위한 API입니다. JDBC는 자바 애플리케이션과 다양한 데이터베이스 시스템 간의 표준 인터페이스를 제공하여, 데이터베이스에 접근하고 데이터를 관리할 수 있도록 합니다.
JDBC는 데이터베이스 연동을 위한 드라이버를 제공하며, 이 드라이버는 각 데이터베이스 제조사에서 제공됩니다. 이 드라이버를 이용하여, JDBC는 SQL 쿼리를 실행하고 결과를 가져올 수 있습니다. 또한, JDBC는 트랜잭션 관리, 저장 프로시저 호출, 메타데이터 조회 등 다양한 데이터베이스 관련 작업을 수행할 수 있습니다.
JDBC는 Java SE의 일부분으로 포함되어 있기 때문에, 자바 개발자는 별도의 설치나 설정 없이 JDBC를 사용할 수 있습니다. JDBC는 데이터베이스와의 연결과 쿼리 실행 등 간단한 작업부터 복잡한 데이터베이스 애플리케이션 개발에 이르기까지 다양한 수준의 데이터베이스 연동을 제공합니다.
JPA를 사용하면서 JDBC 커넥션을 직접 사용하거나, 스프링JdbcTemplate, 마이바티스등을 함께 사용 가능
단, 영속성 컨텍스트를 적절한 시점에 강제로 플러시 필요
예) JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트 수동 플러시
[JPQL 기본 문법과 기능]
- JPQL은 객체지향 쿼리 언어다.따라서 테이블을 대상으로 쿼리 하는 것이 아니라 엔티티 객체를 대상으로 쿼리한다.
- JPQL은 SQL을 추상화해서 특정데이터베이스 SQL에 의존하 지 않는다.
- JPQL은 결국 SQL로 변환된다.
객체 모델과 실제 테이블 간의 차이
![](https://blog.kakaocdn.net/dn/bQERVh/btr27lBvlap/5xnCb3UfGzr4Dmt2yukDkK/img.png)
JPQL 문법
select m from Member as m where m.age > 18
엔티티와 속성은 대소문자 구분O (Member, age)
JPQL 키워드는 대소문자 구분X (SELECT, FROM, where) 엔티티 이름 사용, 테이블 이름이 아님(Member)
별칭은 필수(m) (as는 생략가능)
![](https://blog.kakaocdn.net/dn/BoBSy/btr3argRzd6/j8UQAMXEJ5uwt0oVmzOJI0/img.png)
이외에도 group by, having, order by 등도 사용 가능.
query.getResultList(): 결과가 하나 이상일 때, 리스트 반환 결과가 없으면 빈 리스트 반환
query.getSingleResult(): 결과가 정확히 하나, 단일 객체 반환 결과가 없으면: javax.persistence.NoResultException
둘 이상이면: javax.persistence.NonUniqueResultException.
결과가 정확히 1개일 때만 사용.( 결과가 없을 때도 exception이 발생하는 것이 문제.)
파라미터 바인딩
이름 기준
![](https://blog.kakaocdn.net/dn/d6Mw3G/btr3gNYaogm/LXse5E4gDP3M9UK7QYMsJ0/img.png)
위치 기준 (비 추천)
![](https://blog.kakaocdn.net/dn/uo9Ve/btr3efgBWgp/DjeuUD10KtKRkA9Uuygewk/img.png)
활용 예시)
List<Member> resultList =
em.createQuery("select m from Member m where m.username =:username", Member.class)
.setParameter("username", "shyswy")
.getResultList();
username 에 set된 파라미터 username과 일치하는 이름만 select하는 쿼리.
.setParameter: 특정 조건을 충족하는 데이터를 조회할 때, 파라미터로 특정 조건을 표현
ex) 팀 A 소속인 사람만 선택 -> TeamName 값을 .setParameter 로 설정 후, =:TeamName으로 비교.
프로젝션: select 절에서 조회 대상 지정
SELECT m FROM Member m -> 엔티티 프로젝션: 선택된 모든 엔티티 객체가 영속성 컨텍스트에 관리됨.
SELECT m.team FROM Member m -> 엔티티 프로젝션 -> createquery(~~~,Team.class) 로 가져와야 한다. -> join 쿼리를 통해 가져오게된다.
SELECT m.address FROM Member m -> 임베디드 타입 프로젝션
SELECT m.username, m.age FROM Member m -> 스칼라 타입 프로젝션: 여러가지를 섞어서 가져옴.
-> 어떻게 가져올까?
1) Query 타입으로 가져오기
2) Object[] 타입으로 가져오기
3) new로 조회( 권장 ): 원하는 필드들을 DTO로 가져온다.
em.createQuery("select new jpql.MemberDTO( m.username,m.age) from Member m");
- 단순값을DTO로바로조회
- SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member
- 패키지명을포함한전체클래스명입력
- 순서와 타입이 일치하는 생성자 필요.
select distinct ~~~ 로 중복 제거
페이징 API
JPA는 페이징을 다음 두 API로 추상화
- setFirstResult(int startPosition) : 조회 시작 위치 (0부터 시작)
- setMaxResults(int maxResult) : 조회할 데이터 수
//페이징 쿼리
String jpql = "select m from Member m order by m.name desc";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setFirstResult(10)
.setMaxResults(20)
.getResultList();
내부 join. (교집합 영역. 공통되는 컬럼만 뽑는다.)
외부 조인: left join -> left ( outer ) join: 왼쪽 영역의 모든 레코드 포함. 만약 오른쪽에 일치하는 레코드 없으면, null로 비워둔다.
세타 조인: where ~~~ 로 조건 부여.
ON절을 활용한 조인(JPA 2.1부터 지원)
1. 조인 대상 필터링
2. 연관관계 없는 엔티티 외부 조인(하이버네이트 5.1부터)
예) 회원과 팀을 조인하면서, 팀 이름이 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'
On을 통한 join VS where을 통한 join
아래는 SQL문 동작 순서이다.
![](https://blog.kakaocdn.net/dn/1NirA/btr3ftllV6q/uw4fYrcoQwz3tiyKcYKwjK/img.png)
즉, On을 통한 join 조건 수행 시, join 이전에 조건에 맞는 결과들이 필터링 되고, where 절은 join을 수행한 뒤, 필터링 한다.
DB관점에서 최대한 빨리 Projection 등을 통해 데이터를 필터링 하는 것이 성능적으로 이득이기에, On을 통한 join이 상대적인 장점을 가진다.
ex) A Join B ON A.name=B.name; ->. A.name과 B.name이 일치하는 A,B 의 튜플들만 걸러낸 뒤 Join.
A join B where A.name=B.name; -> A,B join 후 둘의 name이 일치하는 튜플만 걸러낸다.
서브쿼리
() 로 서브쿼리를 묶는다.
나이가 평균보다 많은 회원
select m from Member m
where m.age > (select avg(m2.age) from Member m2)
같은 Member 엔티티를 사용하더라도, alias를 통해 구분해줘야한다. ( m, m2)
한 건이라도 주문한 고객
select m from Member m
where (select count(o) from Order o where m = o.member) > 0
![](https://blog.kakaocdn.net/dn/deJKtZ/btr3eV3iwxV/JIlK9o6utbkvPMQWPlUKd1/img.png)
JPQL의 한계점
JPA는 WHERE, HAVING 절에서만 서브 쿼리 사용 가능 SELECT 절도 가능(하이버네이트에서 지원-> 하이버네이트 6부턴 from 서브쿼리 가능)
FROM 절의 서브 쿼리는 현재 JPQL에서 불가능
조인으로 풀 수 있으면 풀어서 해결
![](https://blog.kakaocdn.net/dn/b0eVEH/btr3dcLqDaG/pEJKxt0yJjeVzTX3HOSw81/img.png)
enum 타입 표현 예시
보통 가독성을 위해 .setParameter() 로 파라미터 바인딩으로 패키지 경로를 넣는다. ( Querydsl로 가면 해당 enum 클래스를 import하여 사용하는 것으로 쉽게 해결 가능 )
엔티티 타입 표현 예시
다형성( 상속관계를 기반으로 Item -> book 변환가능 한 경우를 찾을 수 있다. ( item의 dType=Book 인 경우로 나오게 된다)
SQL과 문법이 같은 식( 대부분 지원한다)
EXISTS, IN
AND, OR, NOT
=, >, >=, <, <=, <> BETWEEN, LIKE, IS NULL
Case문: Case, switch 문과 유사
coalesce: 이름 존재 x 시 해당 필드를 '이름 없는 회원' 으로 채워서 반환, 존재하면 그대로 반환
jpql 함수
JPA에서 이외에도 DB에 종속적으로 다양한 함수를 적용 가능.
CONCAT ( A || B) : 문자열을 연결해 새로운 문자열 생성
TRIM: 좌 우 공백 제거
LOCATE: 문자열 내에서 특정 문자(열)의 위치를 찾는 함수
SELECT LOCATE('world', 'hello world') FROM SomeTable
위 쿼리의 결과는 7이 된다. 이는 "hello world" 문자열에서 "world" 문자열이 7번째 위치에서 시작한다는 것을 의미한다.
SIZE: 컬렉션의 사이즈 반환
ex) Team이 Member와 1 : 다 의 관계를 가지면 ,Team에는 List<Member> members 와 같이 다수의 member를 저장해놓은 컬렉션이 있을 것이다.
select size(t.members) From Team t; 와 같이 그 크기를 받아볼 수 있다.
사용 중인 dialect(방언)를 상속 받고, 사용자 함수를 정의하자. ( 해당 dialect 클래스를 뜯어보면 어떻게 등록되는지 알아 볼 수 있다)
Hibernate 사용 시 group_concat(m.username) 와 같이 기본적 함수 사용하는 것처럼 사용도 가능.
'Spring boot' 카테고리의 다른 글
[스프링 MVC] Part 1 (웹 어플리케이션의 이해) (0) | 2023.03.14 |
---|---|
[JPA] Part 6 (객체지향 쿼리 언어 pt.2) (0) | 2023.03.13 |
[JPA] Part 4 (값 타입) (0) | 2023.03.10 |
[JPA] Part 3 프록시와 연관관계 (0) | 2023.03.10 |
[JPA] Part2 (심화 매핑) (0) | 2023.03.10 |