공부 내용을 정리하고 앞으로의 학습에 이해를 돕기 위해 작성합니다.
조인
//내부 조인
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
내부 조인 예시코드
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member");
member.setAge(10);
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
String query = "select m from Member m inner join m.team t";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
tx.commit();
- join 쿼리 이후 select 쿼리가 한번 더 발생했다.
- JPQL은 JPA와 다르게 SQL 그대로 해석이 된다. select에서 가져오는 값은 Member 뿐이다.
- 따라서 이후에 Team을 조회하기 위해 쿼리가 한번 더 발생하는 것이다.
- 이는 Member에 지연로딩을 설정하면 해결 된다.
1:N 관계에서는 항상 지연로딩으로 설정하자.
다시 돌려보면
의도한 쿼리만 나가는 것을 확인할 수 있다.
외부 조인 예시코드
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member");
member.setAge(10);
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
String query = "select m from Member m left outer join m.team t";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
tx.commit();
세타 조인 예시코드
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member();
member.setUsername("member");
member.setAge(10);
member.setTeam(team);
em.persist(member);
em.flush();
em.clear();
String query = "select m from Member m, Team t where m.username = t.name";
List<Member> result = em.createQuery(query, Member.class).getResultList();
tx.commit();
조인 - ON 절
1. 조인 대상 필터링
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'
2. 연관관계없는 엔티티 외부 조인(하이버네이트 5.1부터)
ex) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
JPQL:
SELECT m, t FROM
Member m LEFT JOIN Team t on m.username = t.name
SQL:
SELECT m.*, t.* FROM
Member m LEFT JOIN Team t ON m.username = t.name
서브쿼리
나이가 평균보다 많은 회원
select m from Member m
where m.age > (select avg(m2.age) from Member m2)
- 서브쿼리에서 Member를 새로 만들어서 사용했다. 즉 메인쿼리와 서브쿼리는 전혀 관계가 없다.
한 건이라도 주문한 고객
select m from Member m
where (select count(o) from Order o where m = o.member) > 0
- 서브쿼리에서 메인쿼리의 Member를 사용했다. 이러면 성능이 잘 안 나온다.
서브 쿼리 지원 함수
팀 A 소속인 회원 ([NOT] EXISTS (subquery): 서브쿼리에 결과가 존재하면 참)
select m from Member m
where exists (select t from m.team t where t.name = ‘팀A')
전체 상품 각각의 재고보다 주문량이 많은 주문들 (ALL 모두 만족하면 참)
select o from Order o
where o.orderAmount > ALL (select p.stockAmount from Product p)
어떤 팀이든 팀에 소속된 회원 (ANY, SOME: 같은 의미, 조건을 하나라도 만족하면 참)
select m from Member m
where m.team = ANY (select t from Team t)
[NOT] IN (subquery): 서브쿼리의 결과 중 하나라도 같은 것이 있으면 참
JPA 서브 쿼리 한계
- JPA는 WHERE 절과 HAVING 절에서 서브쿼리를 사용할 수 있다.
- 일부 구현체, 예를 들어 하이버네이트 6 이상에서는 SELECT 절뿐만 아니라 FROM 절에서도 서브쿼리를 지원한다.
- JPA 표준 JPQL에서는 FROM 절에서 서브쿼리를 사용할 수 없다.
- FROM 절의 서브쿼리를 사용해야 하는 경우, JPA 표준에서는 조인으로 문제를 해결할 수 있다.
JPQL 타입 표현과 기타식
타입 표현
- 문자: ‘HELLO’, ‘She’’s’
- 숫자: 10L(Long), 10D(Double), 10F(Float)
- Boolean: TRUE, FALSE
- ENUM: jpabook.MemberType.Admin (패키지명 포함해야 한다.)
select m.username, 'HELLO', TRUE from Member m where m.type = jpql.MemberType.ADMIN
- 엔티티 타입: TYPE(m) = Member (상속 관계에서 사용한다.)
em.createQuery("select i from Item i where type(i) = Book, Item.class");
JPQL 기타
- SQL과 문법이 같은 식
- EXISTS, IN
- AND, OR, NOT
- =, >, >=, <, <=, <>
- BETWEEN, LIKE, IS NULL
조건식(CASE 등등)
기본 CASE 식
select
case when m.age <= 10 then '학생요금'
when m.age >= 60 then '경로요금'
else '일반요금'
end
from Member m
단순 CASE 식
select
case t.name
when '팀A' then '인센티브110%'
when '팀B' then '인센티브120%'
else '인센티브105%'
end
from Team t
COALESE: 하나씩 조회해서 null이 아니면 반환
// 사용자 이름이 없으면 이름 없는 회원을 반환
select coalesce(m.username,'이름 없는 회원') from Member m
NULLIF : 두 값이 모두 같으면 null 반환, 다르면 첫 번째 값 반환
// 사용자 이름이 '관리자'면 null을 반환하고 나머지는 본인의 이름을 반환
select NULLIF(m.username, '관리자') from Member m
JPQL 함수
CONCAT
select concat('a', 'b') From Member m //ab
SUBSTRING
select substring("관리자2", 2, 3) // -> 리자2
TRIM
select TRIM(" 유저 ") // "유저"
LOWER, UPPER
select LOWER('small'), UPPER('BIG') //SMALL, big
LEGNTH
select LENGTH('asdasdasd') //9
LOCATE
select locate('de', 'abcdegf') //4
ABS, SORT, MOD
select ABS(-10) // 10 (절대값)
select SQRT(16) //4 (제곱근)
select MOD(4, 2) // 0 (나머지)
SIZE, INDEX(JPA 용도, 웬만하면 쓰지 말 것)
select size(t.members) From Team t; //0
사용자 정의 함수 호출
- 하이버네이트는 사용 전 방언에 추가해야 한다.
사용자 정의 함수를 만든다.
package custom;
import org.hibernate.boot.model.FunctionContributions;
import org.hibernate.boot.model.FunctionContributor;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.type.StandardBasicTypes;
public class CustomFunctionContributor implements FunctionContributor {
@Override
public void contributeFunctions(FunctionContributions functionContributions) {
functionContributions.getFunctionRegistry()
.register("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}
}
META-INF -> services 밑에 파일을 생성했던 사용자 정의 함수 명으로 만든다.
<!-- <property name="hibernate.custom" value="org.hibernate.custom.H2Dialect"/>-->
<property name="hibernate.custom" value="custom.MyH2Dialect"/>
- xml에 MyH2Dialect 속성을 추가한다.
이제 사용해 보자.
Team team1 = new Team();
team1.setName("TeamA");
em.persist(team1);
Member member1 = new Member();
member1.setUsername("관리자1");
member1.setTeam(team1);
team1.getMembers().add(member1);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("관리자2");
member2.setTeam(team1);
team1.getMembers().add(member2);
em.persist(member2);
em.flush();
em.clear();
String query = "select function('group_concat', m.username) From Member m";
List<String> result = em.createQuery(query, String.class)
.getResultList();
for (String s : result) {
System.out.println("s = " + s);
}
tx.commit();
- group_concat 함수는 여러 행의 값을 하나의 문자열로 결합해준다.
'BackEnd > JPA' 카테고리의 다른 글
[JPA] 객체지향 쿼리 언어2 - 중급 문법(2) (0) | 2024.07.21 |
---|---|
[JPA] 객체지향 쿼리 언어2 - 중급 문법(1) (0) | 2024.07.21 |
[JPA] 객체지향 쿼리 언어1 - 기본 문법(1) (0) | 2024.07.19 |
[JPA] 값 타입(2) (0) | 2024.07.18 |
[JPA] 값 타입(1) (0) | 2024.07.17 |