<<객체지향 쿼리 언어 개요 >>
결국 DB 조회를 위해선 쿼리를 짤 수밖에 없는데, JPQL, 네이티브 SQL, Query DSL 등 다양한 방법을 지원
JPA를 사용하면 결국 Entity 중심으로 개발할 수밖에 없음.
> 검색을 할때도 테이블이 아닌 Entity 를 대상으로 검색을 해야해함.
> 그렇다면 뭐,, DB 데이터를 다~ 들고 온다음에 메모리에서 다~ 실행해서 객체로 변환한 다음에 Entity 대상으로 검색을 할 수 있도록 해야하나? > 이거는 말이 안됨.
> App이 필요한 Data만 DB에서 불러오려면 결국 ! SQL을 찍어야 함.
JPA + SQL 을 지원하는 JPQL 을 지원함. > JPQL은 일반 SQL처럼 DB 테이블 대상으로 하는게 아니고, 객체를 대상으로 한다. 니가 하던게 다 Entity 대상으로 쿼리 짠거임. 하지만 원래 SQL 은 데이터 중심임.
try{
List<Member> =
em.createQuery("select m from Member m where m.username like '%kim%'", Member.class)
.getResultList();
tx.commit
} ...
저기서 말하는 Member 는 객체를 말하는 것이다. 객체에 대한 조회를 하는 것임.
이런식으로 짜는거임. 하지만 이렇게 String 으로 하나 하나 다 짜는건 버그나기 너무 좋음. 사소한 버그 ㅇㅇ 동적 쿼리를 잘 짤 수 있도록 지원하는게 등장하기 시작 - Criteria, MyBatis 등
Criteria - 코드로 SQL을 짤 수 있도록 해줘서 편리하지만, 한거 보면 상당히 복잡함. (동적 쿼리 짜기도 좋음)
(동적 쿼리란, 코드를 통해서 if 문 걸고 하면서 동적으로 변경될 수 있는 상황을 마련한 코드를 말한다)
예를 들면, if userGender = girl, sql 이 바뀌고 그런것.
Criteria 는 좋긴 한데 유지 보수는 안좋음. - 나는 안쓸거임.
유명한건 이제 MyBatis와 QueryDsl임. QDSL 은 굉장히 문법이 간단함 ( 많이 써봤으니까 알거임) - 단점 세팅이 빡셈 ㅋㅋㅋ - 나는 아직 안해본 것 같은데, QueryDSL 은 동적쿼리를 짜기도 매우 편리. 실무에서 사용하기 권장한다.
자바 코드로 JPQL을 작성할 수 있다는게 제일 큰 장점. 컴파일 시 오류 찾을 수 있음.
근데 QueryDSL 은 결국 JPQL 빌더임!! 그래서 결론적으로 JPA 쓸거면 JPQL 을 잘 알아야함. 그러면 QDSL은 어떻게 쓰는지를 알려주는 거임. JPQL 마스터 하고 QDSL 설명서 (사이트) 보면 다 들어옴.
참고))) 영컨 = Flush 가 되어야 SQL이 날라감. 이걸 항상 명심해야 함. 은근 개입되어 헷갈릴때가 있더라.
>> em.persist 만 한 시점에서는 DB에 안들어가 있음.
>> flush 가 날라가는 경우 1) commit 할 때 2) Query 실행할 때 (JPQL 같은게 실행되면 플러시가 동작함) (왜냐하면 날라가야 DB에서 조회를 할거 아님)
--------------------------
<< JPQL 개요 및 기본 >>
JPQL (Java Persistence Query Lang . 영속성 관리 QL)
> 말했듯이 엔티티 객체를 대상으로 쿼리잉을 한다.
> SQL을 추상화해서 특정 DB SQL에 의존하지 않는다.
> 결국 JPQL 도 당연히 SQL로 치환된다.
- 1분대 모델 참고 -
- 모델링 다 끝 -
select m from Member as m where m.age > 18
> Member 는 Entity 이고, age 는 속성이다.
> 대소문자는 반드시 구분한다
> 별칭 m 은 필수입니다. (as 는 없어도 됨)
@Test
@DisplayName("JPQL Query Example")
void test1() {
// ...
JpqlMember member = new JpqlMember();
member.setUsername("JPQL 1");
member.setAge(20);
em.persist(member);
// 참고하면 좋은 내용
// Member Generic 을 정확하게 가져온다. Type 정보를 명확하게 지정했기 때문.
TypedQuery<JpqlMember> typedQuery = em.createQuery("select m from JpqlMember m", JpqlMember.class);
// 그렇게 안되는 경우 (String, Integer 를 동시에 불러옴) >> Query 객체를 사용 // 반환 타입이 명확하지 않을 경우
Query query = em.createQuery("select m.username, m.age from JpqlMember m");
// 되는 경우
TypedQuery<String> typedQuery2 = em.createQuery("select m.username from JpqlMember m", String.class);
// 결과를 반환해보자
for (JpqlMember jpqlMember :
typedQuery.getResultList()) {
System.out.println("jpqlMember.getUsername() = " + jpqlMember.getUsername());
}
// ...
}
특징
- em.createQuery 는 Query 를 반환할수도, TypedQuery 를 반환할 수도 있다. Query 를 반환할 경우는 위 상황처럼 반환 타입이 명확하지 않고 여러 개를 반환할 경우이다. TypedQuery 는 위처럼 명확하게 반환하려는 타입이 있을 경우이다.
- getResultList() vs getSingleResult();
말그대로 한개여도, List<ResultType> list = typedQuery.getResultList(); 를 반환하게 된다. 만약 없으면 빈 리스트를 초기화해서 반환해주게 되어서, Null Pointer Exception 이 발생하지 않는다.
반면 getSingleResult() 는 반드시 한개만 반환되어야 하며, 반환 값을 조회하지 못했으면 Null Pointer exception 이 발생한다. 심지어 둘 이상이면 NonUniqueResultException 이 발생한다. >> 써주려면 try catch 로 묶어서 사용해야하는 단점.
==== Parameter Binding
- 위치기반은 걍 알지도 마라. Parameter 이름기반 바인딩!
@Test
@DisplayName("JPQL Example : Parameter binding")
void test2() {
// ...
JpqlMember member = new JpqlMember();
member.setUsername("안녕하세요");
member.setAge(20);
em.persist(member);
// Query 호출시 flush 자동 발생이므로 굳이 직접 안해줘도 됨
TypedQuery<JpqlMember> query = em.createQuery("select m from JpqlMember m where m.username = :username", JpqlMember.class);
query.setParameter("username", "안녕하세요");
JpqlMember singleResult = query.getSingleResult();
System.out.println("singleResult.getUsername() = " + singleResult.getUsername());
tx.commit();
...
}
위와 같이 Param 이 들어갈 곳을 지정해준다음에, 나와서 해당 쿼리에 Params를 등록하게 된다. 위는 QUery 를 보여주려 한거고, 일반적으로 아래와 같이 호출한다.
JpqlMember singleResult1 = em.createQuery("select m from JpqlMember m where m.username = :username", JpqlMember.class)
.setParameter("username", "안녕하세요")
.getSingleResult();
------=====--=-===
<<select 및 프로젝션>>
프로젝션 : select 절에 조회할 대상을 지정하는 것을 말한다. (그냥 항상 하던 거임
> select m from (엔티티 프로젝션)
> select m.team from (엔티티 프로젝션) >> 조회하는 곳이랑 다른 타입을 하면 Join 절이 나가게 됨
> select m.address from (임베디드 타입 프로젝션)
> select m.username, m.age from ( 기본값, 스칼라 타입 프로젝션)
> distinct 절로 중복 제거 ㄱㄴ
> 등등
>>>>> select 절은 조회 받는 모든 데이터들을 영속성 컨텍스트로 관리시켜준다 <<<<<
@Test
@DisplayName("JPQL Example : Select Projection 기능")
void test3() {
// ...
JpqlMember jpqlMember = new JpqlMember();
jpqlMember.setUsername("Fucker");
jpqlMember.setAge(10);
em.persist(jpqlMember);
// DB에 저장
em.flush();
em.clear(); // 영컨 초기화
List<JpqlMember> members = em.createQuery("select m from JpqlMember m", JpqlMember.class)
.getResultList();
members.get(0).setUsername("Bitch");
tx.commit();
//...
}
위와 같이 select Query 로 받아오면 영속성 컨텍스트에서 관리를 시작해주고, set으로 변경사항을 적용 후 commit 하면 쓰기 지연 저장소에서 update query 가 나가게 된다.
@Test
@DisplayName("JPQL Example : Select Projection Embedded")
void test4() {
// ...
JpqlOrder order = new JpqlOrder();
order.setOrderAmount(1);
JpqlAddress address = new JpqlAddress("city", "address");
order.setAddress(address);
em.persist(order);
//
em.flush();
em.clear();
List<JpqlAddress> resultList = em.createQuery("select o.address from JpqlOrder o", JpqlAddress.class)
.getResultList();
for (JpqlAddress singleAddress :
resultList) {
System.out.println("singleAddress.getStreet() = " + singleAddress.getStreet());
}
tx.commit();
// ...
}
위와같이 임베디드 타입에 대해서도 받아올 수 있다.
JpqlAddress 만을 따로 from 해서 가져올 수는 없다. Entity 가 아니기 때문. 주인 Entity 를 통해서 불러와야 함.
<참고로, m.username, m.age 처럼 여러개의 타입일 경우 어떻게 반환해야 하는가?>
1. Object 배열로 받아오는 방법.
List resultList = em.createQuery("select m.username, m.age from JpqlMember m").getResultList();
Object o = resultList.get(0);
Object [] result = (Object []) o;
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
2. Object 배열로 타입 캐스팅을 해서 받아오는 방법
List<Object []> resultList = em.createQuery("select m.username, m.age from JpqlMember m").getResultList();
Object [] result = resultList.get(0);
System.out.println("username = " + result[0]);
System.out.println("age = " + result[1]);
3. new 명령어 (DTO) 로 빠르게 조회하는 방법.
빠르게 Dto 를 선언해서 가져올 수 있다 ( 원하는 것을 가져오는 가장 좋은 방법이며, QueryDsl 에서도 이와 같이 했떤 경험이 있는데, 역시 결국 QueryDsl 은 JPQL 을 만들어주기 위함임)
@Test
@DisplayName("Select projection : DTO 로 가져오기")
void test5() {
// ...
JpqlMember jpqlMember = new JpqlMember();
jpqlMember.setUsername("Fucker");
jpqlMember.setAge(10);
em.persist(jpqlMember);
// DB에 저장
em.flush();
em.clear(); // 영컨 초기화
List<MemberDto> resultList = em.createQuery("select new jpql.MemberDto(m.username, m.age) from JpqlMember m"
, MemberDto.class).getResultList();
MemberDto resultDto = resultList.get(0);
System.out.println("resultDto.getUsername() = " + resultDto.getUsername());
System.out.println("resultDto.getAge() = " + resultDto.getAge());
tx.commit();
//...
}
@Data
@AllArgsConstructor
public class MemberDto {
private String username;
private int age;
}
이와 같이 DTO 를 new 로 선언해서 빠르게 가져올 수 있음.
> 패키지 명을 포함한 전체 클래스명 입력해야 함
> 생성자와 똑같은 순서에 유의해야함
<< 페이징 >>
Paging : 페이징이란 몇 개에서 부터 몇개까지 가져올래, 이게 다임.
@Test
@DisplayName("JPQL PAGING")
void test1() {
//...
for (int i = 0; i < 100; i++) {
JpqlMember member1 = new JpqlMember();
member1.setName("Member" + i);
member1.setAge(i + 10);
em.persist(member1);
}
em.flush();
em.clear();
List<JpqlMember> resultList = em.createQuery("select m from JpqlMember m order by m.age desc", JpqlMember.class)
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
System.out.println("resultList.size() = " + resultList.size());
for (JpqlMember m :
resultList) {
System.out.println("JpqlMember = " + m);
}
tx.commit();
// ...
}
하려고 하는 것 : 현재 JpqlMember 를 age 기준으로 desc 오더로 정렬하라
그리고 0번째가 아닌 첫번째부터 시작해서 10번째 순서인 애들을 출력해라 ( limit, offset 으로 쿼리가 나감)
>> 이거 다른 DB 들 보면 진짜 복잡하게 해결하는 경우 많은데, JPA는 이렇게 간단하게 해줌.
<< JOIN >>
조인은 동일하다 >> 객체스타일로 문법이 나가긴 함.
@Test
@DisplayName("JPQL Join Test")
void test() {
//...
JpqlTeam team1 = new JpqlTeam();
team1.setName("Team 1");
em.persist(team1);
JpqlMember member1 = new JpqlMember();
member1.setName("Member 1");
member1.setAge(10);
member1.changeTeam(team1);
em.persist(member1);
em.flush();
em.clear();
String query = "select m from JpqlMember m inner join m.team t";
List<JpqlMember> resultList = em.createQuery(query, JpqlMember.class)
.getResultList();
//...
}
이와 같은 상황에서 SQL 이 출력되는 것을 보면,
select (all _ val) from member m inner join team t on t.team_id = m.member_id; 이런식으로 나가는 것을 볼 수 있음.
******** ON 조건 절 지원
1. 조인 대상을 필터링 할 수 있다.
2. 연관관계 없는 엔티티 외부 조인 가능 (이거 대문에 네이티브 쿼리 짜는 경우 많은데, 이거 지원 함)
>> 뭔소리냐?
1번 예시 >> 회원과 팀을 조인하면서 팀 이름이 A 인 팀만 조인,
sql 을 이렇게 만들고 싶은 거임 : select m.*, t.* from Member m join Team t on m.team_id = t.team_id and t.name = 'A';
추가적인 ON 절로 내보내는 것
******** 서브쿼리
> 쿼리 안에 또다른 쿼리가 있는 것. ~IN, ~NOT IN 등 결과에 ~ 가 있으면 이런식으로 많이 씀.
> JPA 는 WHERE / HAVING 절에서만 서브 쿼리 가능. Hibernate 에서 SELECT 도 지원해줌
select 예시 : "select (select avg(m1.age) From Member m1) as avAge from Member m join Team t on m.username = t.username)?
> FROM 절의 서브 쿼리는 현재 JPQL 에서 불가능 / 조인으로 풀 수 있으면 풀어서 해결.
> 잘 모르겠지만, FROM 절 서브쿼리가 ㅈㄴ 쓰고 싶다? >> 네이티브 SQL을 씀. 아니면 불러온 다음에 앱에서 조작. 근데 보통 그 방법 외에는 없는 경우는 잘 없긴 하다고 함. .
********** JPQL 타입 표현과 기타식
********** 조건식 (CASE 식)
기본 CASE : 특정 조건이 만족하면 결과를 ~게 낸다
단수 CASE : EXACT 매칭일 시 결과를 ~게 낸다.
>> DB에 있는 값이 아니더라도, 다음과 같은 CASE 문을 통해서 원하는 값들을 Parsing 할 수 있다
String query =
"select "
+ "case when m.age <=10 then '학생요금' "
+ " when m.age >=60 then '경로요금' "
+ " else '일반요금' end "
+ "from JpqlMember m";
List<String> results = em.createQuery(query, String.class)
.getResultList();
for (String s : results) {
System.out.println(s);
}
> 위와 같이 하면 지속되던 age = 10 인 예제로는 '학생요금' 만이 출력될 것이다.
>> COALESCE : 하나씩 조회해서 NULL이 아니면 반환, NULL이면 지정 값 반환 or no 반환
String query = "select coalesce(m.username, '이름 없는 회원') from JpqlMember m";
List<String> results = em.createQuery(query, String.class)
.getResultList();
for (String s :
results) {
System.out.println("s = " + s);
}
> 위와 같은 예제에서 m.username 이 NULL 인 회원은 '이름 없는 회원'이, 아닐경우는 m.usernam 이 출력된다.
>> NULLIF : 두 값이 같으면 NULL 반환. 아닐 경우 원하는 값 반환
String query2 = "select nullif(m2.username, '관리자') from JpqlMember m2";
이렇게 짤 경우 m2.username 이 '관리자'일 경우 NULL 이라고 반환하게 된다. (좀 특이하기도 하고, coalesce 와 비슷한 것 같긴 한데 좀 다름)
>>> nullif 와 coalesce 처럼, 사용 가능한 함수들이 있다 (JPQL 기본)
: CONCAT, SUBSTRING, TRIM, LOWER, UPPER, LENGTH, LOCATE, ABS, SQRT, MOD, SIZE, INDEX 등등 ( MYSQL DIALECT 에 들어가면 MYSQL 한정 가능한 함수들도 많음)
size 같은 경우
'다' 연관관계에 매핑되었을 경우 그 묶인 사이즈를 반환함.
> ex: select sizeof(t.members) from JqplTeam t 라고 하면 t.members 로 반환되는 Members들의 수를 반환함.
'Spring > JPA' 카테고리의 다른 글
| [JPA] - 나머지 쿼리 (0) | 2023.01.04 |
|---|---|
| [JPA] JPQL 쿼리 2 (FETCH JOIN의 등장) (0) | 2023.01.03 |
| [JPA] - 값 타입 (0) | 2022.12.29 |
| [JPA] 프록시와 연관관계 - 2 (0) | 2022.12.28 |
| [JPA] 프록시와 연관관계 정리 (0) | 2022.12.26 |