경로 표현식 : .
을 찍어 객체 그래프를 탐색
select m.username // 상태 필드
from Member m
join m.team t // 단일 값 연관 필드
join m.orders o // 컬렉션 값 연관 필드
where t.name = '팀A'
상태 필드 : 단순히 값을 저장하기 위한 필드
연관 필드 : 연관관계를 위한 필드
@ManyToOne
, @OneToOne
대상이 엔티티@OneToMany
, @ManyToMany
대상이 컬렉션💋특징
from
절에서 명시적 조인을 통해 별칭을 얻으면 탐색 가능명시적, 묵시적 조인
join
키워드 사용select t from Member m join m.team t
select m.team from Member m
fetch join
1) 엔티티 페치 조인
회원을 조회하면서 연관된 팀도 함께 조회하고 싶다
select m from Member m join fetch m.team
select m.*, t.* from member m inner join team t on m.team_id = t.id
예제 살펴보기
try {
Team team1 = new Team();
team1.setName("팀A");
em.persist(team1);
Team team2 = new Team();
team2.setName("팀B");
em.persist(team2);
Team team3 = new Team();
team3.setName("팀C");
em.persist(team3);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(team1);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(team1);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(team2);
em.persist(member3);
Member member4 = new Member();
member4.setUsername("회원4");
em.persist(member4);
em.flush(); //db에 반영
em.clear(); //영속성 컨텍스트 비우기
/**
* query = "select m from Member m"
* 쿼리 => N + 1(처음 멤버)
* 팀A => SQL
* 팀A => 1차캐시(영속성컨텍스트)
* 팀B => SQL
*/
String query = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(query, Member.class)
.getResultList();
for (Member member : members) {
System.out.println("회원 : " + member.getUsername() +
"\t" + "속한 팀 : " + member.getTeam().getName());
}
tx.commit();
}
print:
회원 : 회원1 속한 팀 : 팀A
회원 : 회원2 속한 팀 : 팀A
회원 : 회원3 속한 팀 : 팀B
//But, fetch join으로 sql은 한 번 생성
2) 컬렉션 페치 조인
일대다 관계, 데이터가 뻥튀기 될 수 있다.
JPQL : selelct t from Team t join fetch t.members where t.name = '팀A'
SQL : select t.*, m.* from team t inner join member m on t.id = m.team_id where t.name = '팀A'
예제 살펴보기)
try {
Team team1 = new Team();
team1.setName("팀A");
em.persist(team1);
Team team2 = new Team();
team2.setName("팀B");
em.persist(team2);
Team team3 = new Team();
team3.setName("팀C");
em.persist(team3);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(team1);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(team1);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(team2);
em.persist(member3);
Member member4 = new Member();
member4.setUsername("회원4");
em.persist(member4);
em.flush(); //db에 반영
em.clear(); //영속성 컨텍스트 비우기
String query = "select t from Team t join fetch t.members";
List<Team> teams = em.createQuery(query, Team.class)
.getResultList();
for (Team team : teams) {
System.out.println("<" + team.getName() + ">에 속한 회원수 : " + team.getMembers().size());
}
tx.commit();
}
print:
<팀A>에 속한 회원수 : 2
<팀A>에 속한 회원수 : 2
<팀B>에 속한 회원수 : 1
//fetch join으로 sql은 한 번 생성
//중복 결과 이유 : 팀에 속한 Member가 2개
페치 조인과 DISTINCT
JPQL의 2가지 기능
예제로 살펴보기!
위의 query
문을 "select distinct t from Team t join fetch t.members"
로 변경해보자.
💥SQL에 distinct를 추가했지만 사실, 데이터가 다르므로 DB상에서는 중복 제거를 하지 못한다.
💋(중요) But, 애플리케이션에서는 똑같은 식별자를 갖는 Team 엔티티를 제거한다.
print:
<팀A>에 속한 회원수 : 2
<팀B>에 속한 회원수 : 1
페치 조인과 일반 조인의 차이
1) 일반 조인 실행시 연관된 엔티티를 함께 조회하지 않는다.
2) 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
한계
💥페치 조인 대상에는 별칭을 줄 수 없다.
selelct t from Team t fetch join t.members as m
(X)💥둘 이상의 컬렉션은 페치 조인 할 수 없다.
💥컬렉션을 페치 조인하면 페이징 API (setFristResult
, setMaxResults
)를 사용할 수 없다.
XXXToOne
과 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능
Hibernate는 경고 로그를 남기고 메모리에서 페이징(💥매우 치명적 위험)
select t from Team t fetch join t.member
⇒ select m from Member m fetch join m.team
⇒ 일대다에서 다대일 페치 조인이 형성되기 때문에 페이징 API를 사용해도 된다.
OR, select t from Team t
+ **BETCHSIZE**
설정
방법 1) @
을 사용해서 명시
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
방법 2) 설정 파일에 명시
<properties>
<property name="hibernate.default_batch_fetch_size" value="100"/>
</properties>
페치 조인 특징
@OneToMany(fetch = FetchType.LAZY)
정리
테이블 모델
TYPE
select i from Item i where type(i) IN (Book, Movie)
select i from Item i where i.DTYPE in ('B', 'M')
TREAT
select i from Item i where treat(i as Book).auther = 'kim'
select i.* from Item i where i.DTYPE = 'B' and i.auther = 'kim'
기본 키 값
JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용
JPQL
select count(m.id) from Member m
: 엔티티의 아이디를 사용select count(m) from Member m
: 엔티티를 직접 사용SQL : select count(m.id) as cnt from Member m
엔티티를 파라미터로 전달
String jpql = "select m from Member m where m = :member";
List resultList = em.createQuery(jpql)
.setParameter("member", member)
.getResultList();
식별자를 직접 전달
String jpql = "select m from Member m where m.id = :memberId";
List resultList = em.createQuery(jpql)
.setParameter("memberId", member.getId())
.getResultList();
실행된 SQL은 두 경우가 똑같다.
select m.* from Member m where m.id = ?
외래 키 값
엔티티를 파라미터로 전달
Team team = em.find(Team.class, 1L);
String qlString = "select m from Member m where m.team = :team";
List resultList = em.createQuery(qlString)
.setParameter("team", team)
.getResultList();
식별자를 직접 전달
String qlString = "select m from Member m where m.team.id = :teamId";
List resultList = em.createQuery(qlString)
.setParameter("teamId", teamId)
.getResultList();
실행된 SQL은 두 경우가 똑같다.
select m.* from Member m where m.team_id = ?
미리 정의(정적 쿼리)해서 이름을 부여해두고 사용하는 JPQL
@
과 XML에 정의하는 두 가지 방법
애플리케이션 로딩 시점에 초기화 후 재사용
🧨애플리케이션 로딩 시점에 쿼리 검증
애노테이션 @
사용법
@Entity
@NamedQuery(
name = "Member.findByUsername",
query="select m from Member m where m.username = :username")
public class Member {
...
}
List<Member> resultList =
em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", "회원1")
.getResultList();
XML 사용법
META-INF\persistence.xml
<persistence-unit name="jpabook" >
<mapping-file>META-INF/ormMember.xml</mapping-file>
META-INF/ormMember.xml
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
<named-query name="Member.findByUsername">
<query><![CDATA[
select m
from Member m
where m.username = :username
]]></query>
</named-query>
<named-query name="Member.count">
<query>select count(m) from Member m</query>
</named-query>
</entity-mappings>
Named 쿼리 환경에 따른 설정
@
우선권ex) 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
벌크 연산 : 쿼리 한 번으로 여러 테이블 로우 변경(엔티티)
executeUpdate()
의 결과는 영향받은 엔티티 수 반환
UPDATE, DELETE 지원
INSERT(insert into ... select, 하이버네이트 지원)
모든 멤버의 나이를 20으로 설정
//query 생성될때 자동 flush
int resultCnt = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
System.out.println("변경된 데이터 수 = " + resultCnt);
System.out.println("회원 1의 나이 = " + member1.getAge());
System.out.println("회원 2의 나이 = " + member2.getAge());
System.out.println("회원 3의 나이 = " + member3.getAge());
System.out.println("회원 4의 나이 = " + member4.getAge());
출력
print:
변경된 데이터 수 = 4
회원 1의 나이 = 0
회원 2의 나이 = 0
회원 3의 나이 = 0
회원 4의 나이 = 0
DB
벌크 연산 주의할 점
em.flush()
, em.clear()