인프런 김영한님의 Java ORM 표준 JPA 프로그래밍에서 다루는 JPQL 부분입니다. 오타주의, 구어체 주의
JPA는 다양한 쿼리 방법을 지원함 : JPQL, JPA Criteria, QueryDSL, 네이티브 SQL - 특정 디비에 종속적,JDBC API 직접 사용, 마이바티스 등등
대부분의 경우 JPQL로 해결 가능, 아주 소수의 경우 나머지로 활용.
지금까지 가장 단순한 조회방법 : EntityManeger.find(), 객체 그래프 탐색.
나이가 18살 이상인 회원을 모두 가져오고 싶다면?
JPA를 사용하면 엔티티 객체를 중심으로 개발. 디비는 존재만 할 뿐
문제는 검색 쿼리!
검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색
모든 db를 객체로 변환해서 검색 쌉불가능
어플리케이션이 필요한 데이터만 디비에서 불러오려면 결국 검색 조건이 포함된 sql이 필요
PA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공.
JPQL은 엔티티 객체 대상을 하는 쿼리. 결국 sql로 변환됨.
Ex)
em.createQuery(“select m from member(<-테이블이 아님, 엔티티) m where m.username like ~ ”)
=> sql로 번역됨.
차이점이 몇가지가 있을 뿐. Select m 은 멤버 객체 자체를 조회해 와! 하는 의미
테이블이 아닌 객체를 대상으로 하는 쿼리.
“객체지향 sql”
쿼리는 사실 그냥 문자열일 뿐. 그러면 동적으로 쿼리를 생성하기가 어려움…
String username;
If (username != null) {String where = “where m.username like ‘%Kim’’; qlstring + where ….. ’}
//이러면 막 띄어쓰기 무슨 뭐 글자 바꾸기 이런거가 너무 빡셈. 딱 버그나기 너무 좋음.
//Criteria 사용 준비 - java 표준 스펙에서 지원하는 문법
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
Root<Member> m = query.from(Member.class);
query.select(m).where(cb.equal(m.get(“username”))
실무에서 안씀ㅋㅋ 아 이런게 있구나. 이건 좀 망한 스펙입니다. 내가 쓴 코드를 내가 못알아봐요. 유지보수 쌉불가능. 차라리 스트링을 더하는게 나을 정도.
QueryDSL 사용. 요거는 김영한님 QueryDSL 강의에서.
문자가 아닌 자바 코드로 JPQL을 작성
동적 쿼리 작성이 편리. 단순하고 쉬움, 실무 사용 권장. 이걸 기본으로 깔고 들어갑니다. 동적 쿼리에서 강하기 때문.
중요한건 jpql 문법을 알면 됩니다. QueryDSL은 그러면 보너스처럼 먹고 들어간다고 보면 됩니다.
뜬금없긴 한데, Jpa와 관련 기술이 아닌 경우 플러시를 해줘야 하는거 잊지 말기.
*결론 jpql 문법 지리게 공부하고, querydsl이랑 같이 쓰세요.
Select
count(m)
sum(m)
~
Groub py, having 그대로 사용 가능
TypeQuery<Member> query = em.createQuery(“select m from Member m”, Member.class)
TypeQuery<String> query = em.createQuery(“select m.username from Member m”,String.class)
Query : 반환 타입이 명확하지 않을 때 사용
TypeQuery<Member> query = em.createQuery(“select m from Member m”, Member.class)
1) List resultlist = query.getResultList();
결과가 없으면 빈 컬렉션이 반환됨
널 포인터에 면역이 좀 있음.
2) Member result = query.getSingleResult();
TypeQuery<Member> query = em.createQuery(“select m from Member m where m.username = :username”, Member.class)
query.setParameter(“username”,”member1”);
.setParameter(“username,”member1”)
.getSingleResult() //체이닝
// 위치기반으로 쓸 수도 있지만, 별롭니다. 이름 기준으로 쓰세용.
프로젝션 대상 : 엔티티, 임베디드 타입, 스칼라타입 (숫자, 문자 등 기본 데이터 타입). rdbms는 스칼라만 가능했는데
select m from member m // 엔티티 프로젝션
Select m.team from member m // 엔티티 프로젝션
Select m.address from member m // 임베디드 타입 프로젝션
Select m.usernmae , m.age from member m // 스칼라타입 프로젝션
//Distinct로 중복 제거
엔티티 프로젝션 : 영속성 컨텍스트로 다 관리가 됨.
List<Member> result = em.createQuery(“select m from member m”,Member.class).getResultList
엔티티 프로젝션
List<Team> result = em.createQuery(“select m.team from member m”, Team.class).getResultList();
List<Team> result = em.createQuery(“select m.team from member m join m.team team”, Team.class).getResultList();
과 같이 명시적으로 조인을 표현해줘야 합니다.
임베디드 타입 프로젝션
em.createQuery(“select o.address from Order o”,Address.class) - 임베디드 타입 프로젝션
List resultList = em.createQuery(“select m.username, m.age from member m”) .getResultList();
Object o = resultList.get(0);
Object[] result = (Object[]) 0;
List<Object[]> resultList = em.createQuery(“select m.usernmae ~~~~”) 이런식으로
memberDTO를 만듬.
//Member DTO class
public class MemberDTO {
private String username;
private String age;
//getter()
//setter()
}
//test file
List<MemberDTO> result = em.createQuery(“select new jpql.MemberDTO(m.username, m.age) from member m”, MemberDTO.class).getResultList();
그나마 깔끔한데 패키지 이름을 new에다가 다 해줘야됨.
querydsl쓰면 됨.
순서와 타입이 일치하는 생성자가 필요함.
아트의 경지입니다… 다른 db쓰면 거지같은데 jpa에서는 환상적으로 해줍니다.
setFirstResult(int startPosition) : 조회 시작 위치
setMax
em.createQuery(“select m from member m order by m.age desc”) - 페이징에서는 오더바이가 들어가야’
.setFirstResult(1)
.setMaxResult(10)
.getResult();
하면…? 에뮬레이팅 해줌. 개꿀쓰~
이 쿼리가 그 persistence.xml에 있는 dialect로 조져줌.
————————————————————————————————————
내부조인 : select m from member m [inner] join m.team t 회원과 연관있는 team을 조인할거야
외부조인 : select m from member m left join m.team t
세타조인 : “막조인”. 이상한 쿼리 날릴 수도 있잖아. 연관관계가 전혀 없는 애를 하고 싶을 때.
String query = “select m from member m inner join m.team t where t.teamnae = :teamName” 이런식으로
String query = “select m from member m left join m.team t where t.teamnae = :teamName” 이런
String query = “select m ffrom member m, team t where m.username = t.teamnae”
연관관계 없는 엔티티 외부 조인 (하이버네이트 5.1), 기존에는 내부 조인만 그랬는데 이제는 외부 조인까지 가능
ex) 회원의 이름과 팀의 이름이 같은 대상 외부 조인 - 스프링 높은 버전을 써야. 5.1 이상의 하이버네이트
jpql :
select m,t from member m left join team t on m.username = t.name
-> 서로 아무 연관관계가 없어도 left join 가능
나이가 평균보다 많은 회원
select m from member m where m.age > (select avg(m2.age) from member m2)
한 건이라도 주문한 고객
select m from member m where (select count(0) from Order o where m = o.member)>0
jpa 서브쿼리의 한계
jpa는 where,having 절에서만 서브 쿼리 사용 가능
select절도 가능. 하이버네이트 지원
from 절의 서브 쿼리는 현재 jpql에서 불가능 - 조인으로 풀 수 있으면 풀어서 해결, 안되면 포기. 안됨.
1) 기본함수 : 디비에 상관없이 jpql이 지원하는 함수.
2) 사용자 정의 함수 : 디비에 함수가 있으면 그걸 가져다가 써야 하는데, jpql은 그 함수를 알 방법이 없음
select function(‘group_concat’,i.name) from item i
근데 이걸 방언으로 등록해놔야함..
하지만, 다행히 이미 그런 디비에서 쓰는 함수를 등록을 해놨다~ 이거야~ 물론 디비 종속적이지만..
//ex)concat
String query = “select ‘a’ || ‘b’ from member m” = concat(‘a’,’b’)
//ex) substring
String query = “select substring(m.username,2,3) from member m” …
//ex) locate
String query = “select locate(‘de’,’abcdefg’) from member m”
//‘de’의 자리 4가 나옴.
ex) length, abs, sqrt, mod
ex) size, index (jpa 용도)
String query = “select size(t.members) from team t” - 컬렉션의 크기를 돌려줌
String query = “select index(t.members) from team t”
@OrderColmn 뭐 이런거..?
경로표현식? : 점을 찍어 객체 그래프를 탐색하는 것
select m.username //상태 필드
from member m
join m.team t // 단일 값 연관 필드(manytooe,onetoone..)
join m.orders o // 컬렉션 값 연관 필드
where t.name = "팀A"
//상태필드로 가냐, 단일 값 연관 필드로 가냐, 컬렉션 값 연관 필드로 가냐에 따라서 결과가 달라짐.
상태필드 : 단순히 값을 저장하기 위한 필드 ex) m.username
연관필드 : 연관 관계를 위한 필드
1) 단일 값 연관 경로 : @ManyToOne, @OneToOne, 대상이 엔티티
- 묵시적 내부조인 발생, 탐색 가능.
- select m.team.username 등등, 탐색 가능.
String query = "select m.team from member m"
List<Team> result = em.createQuery(query,Team.class).getResultList()
2) 컬렉션 값 연관 필드 : @OneToMany, @ManyToMany
결론 : 사실 사 무시하고, 그냥 묵시적 조인을 쓰지 마세요!! 명시적 조인을 써야 합니다. 그래야 쿼리 튜닝이 가능합니다. 묵시적 조인 스면 실무에서 상당한 에러가 발생한다. -> 아 뭐 신경 안쓰면 되겠네.
명시적 조인? : 조인 키워드 직접 사용
묵시적 조인? 경로 표현식에 의해 묵시적으로 sql조인 발생. 외부조인 불가능
예제
select o.member.team from order o //성공,문제는 조인이 두번 일어나겠죠.
select t.members from team // 성공, 컬렉션이지만 더 안들어갔으니까. 끝이니까
select t.members.username from team t //실패, 컬렉션은 더 못들어감
select m.username from team t join t.members m // 조인문을 조져줬기 때문에.
진짜 결론 : 경로 탐색을 사용한 묵시적 조인의 주의사항
1) 묵시적 조인은 항상 내부 조인
2) 컬렉션은 항상 경로 탐색의 끝, 명시적 조인을 통해 별칭을 얻어야.
3) 경로 탐색은 select where에서만 사용하지만 sql에서는 from, where에 영향
-가급적 묵시적 조인 대신에 명시적 조인 사용합시다.
-조인은 sqlㅇ 튜닝에 대한 중요 포인트.
-묵시적 조인은 조인이 일어나는 상황 자체에 대한 판단이 어려움.
하지만 나중에.