select m.username //-> 상태 필드
from Member m
join m.team t //-> 단일 값 연관 필드
join m.orders o //-> 컬렉션 값 연관 필드
where t.name = '팀A';
@ManyToOne, @OneToOne
, 대상이 엔티티(ex: m.team)@OneToMany, @ManyToMany
, 대상이 컬렉션(ex: m.orders)select m.username, m.age from Member m
[JPQL]
String query = "select o.member from Order o";
String qeuryAn = "select m.team from Member m";
//묵시적 내부 조인(단일 값 연관경로)
[SQL]에서 실행되는 쿼리
select m.*
from Orders o
inner join Member m on o.member_id = m.id
select m.*
from Member m
inner join Team t on m.team_id = t.id
[JPQL]
String query = "select t.members from Team t";
//묵시적 내부 조인(컬렉션 값 연관경로)
//컬렉션 값 연관경로 .. 탐색불가..컬렉션 자체가 반환되기에
//대안 : from절을 통한 명시적 조인을 통해 가능
String queryDv = "select m.username from Team t join t.members m";
//명시적 조인 (컬렉션 값 연관경로) - join키워드 직접 사용
//별칭을 통해 탐색 가능
//결론: 묵시적 조인 쓰지 않는다. 쿼리 튜닝하기도 어렵다. 명시적 조인 추천
List resultList = em.createQuery(query, Collection.class).getResultList();
for(Object s : resultList){
System.out.println(s);
}
명시적 조인: join 키워드 직접 사용
select m from Member m join m.team t
묵시적 조인: 경로 표현식에 의해 묵시적으로 SQL 조인 발생(내부 조인만 가능)
select m.team from Member m
경로 탐색을 사용한 묵시적 조인 시 주의사항
가급적 묵시적 조인 대신에 명시적 조인 사용
[ LEFT [OUTER] | INNER ] JOIN FETCH
조인경로EARER LOADING
과 유사하다고 보면 된다.[JPQL]
select m from Member m join fetch m.team
[SQL]
SELECT M.*, T.* FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID=T.ID
[엔티티 페치 조인 사용 코드- 다대일 관계]
조건 : 회원1 - 팀A, 회원2-팀A, 회원3-팀B
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setAge(20);
member1.setMemberType(MemberType.ADMIN);
member1.changeTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setAge(20);
member2.setMemberType(MemberType.ADMIN);
member2.changeTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setAge(20);
member3.setMemberType(MemberType.ADMIN);
member3.changeTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String jpql = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(jpql, Member.class).getResultList();
for(Member memberPer : members){
System.out.println("username = "+memberPer.getUsername()+", "+
"teamName = "+memberPer.getTeam().getName());
}
[출력]
Hibernate:
/* select
m
from
Member m
join
fetch m.team */ select
member0_.id as id1_0_0_,
team1_.id as id1_3_1_,
member0_.age as age2_0_0_,
member0_.TEAM_ID as TEAM_ID5_0_0_,
member0_.type as type3_0_0_,
member0_.username as username4_0_0_,
team1_.name as name2_3_1_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.id
username = 회원1, teamName = 팀A
username = 회원2, teamName = 팀A
username = 회원3, teamName = 팀B
[컬렉션 페치 조인 사용 코드- 일대다 관계]
조건 : 회원1 - 팀A, 회원2-팀A, 회원3-팀B
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setAge(20);
member1.setMemberType(MemberType.ADMIN);
member1.changeTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setAge(20);
member2.setMemberType(MemberType.ADMIN);
member2.changeTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setAge(20);
member3.setMemberType(MemberType.ADMIN);
member3.changeTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String jpql = "select t from Team t join fetch t.members";
List<Team> teamList = em.createQuery(jpql, Team.class).getResultList();
for(Team team : teamList) {
System.out.println("teamname = " + team.getName() + ", team = " + team);
for (Member member : team.getMembers()) {
//페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println("-> username = " + member.getUsername()+ ", member = " + member);
}
}
[출력]
Hibernate:
/* select
t
from
Team t
join
fetch t.members */ select
team0_.id as id1_3_0_,
members1_.id as id1_0_1_,
team0_.name as name2_3_0_,
members1_.age as age2_0_1_,
members1_.TEAM_ID as TEAM_ID5_0_1_,
members1_.type as type3_0_1_,
members1_.username as username4_0_1_,
members1_.TEAM_ID as TEAM_ID5_0_0__,
members1_.id as id1_0_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
teamname = 팀A, team = hellojpa.Team@5d39f2d8
-> username = 회원1, member = Member{id=33, username='회원1', age=20}
-> username = 회원2, member = Member{id=34, username='회원2', age=20}
teamname = 팀A, team = hellojpa.Team@5d39f2d8
-> username = 회원1, member = Member{id=33, username='회원1', age=20}
-> username = 회원2, member = Member{id=34, username='회원2', age=20}
teamname = 팀B, team = hellojpa.Team@458544e0
-> username = 회원3, member = Member{id=35, username='회원3', age=20}
String jpql = "select distinct t from Team t join fetch t.members";
List<Team> teamList = em.createQuery(jpql, Team.class).getResultList();
for(Team team : teamList) {
System.out.println("teamname = " + team.getName() + ", team = " + team);
for (Member member : team.getMembers()) {
//페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println("-> username = " + member.getUsername()+ ", member = " + member);
}
}
[실행결과]
teamname = 팀B, team = hellojpa.Team@6ad6fa53
-> username = 회원3, member = Member{id=40, username='회원3', age=20}
teamname = 팀A, team = hellojpa.Team@458544e0
-> username = 회원1, member = Member{id=38, username='회원1', age=20}
-> username = 회원2, member = Member{id=39, username='회원2', age=20}
중복되지 않게 출력됨을 알 수 있다.
(fetch join 예시)
//[JPQL]
select 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';
String jpqls = "select t From Team t join fetch t.members m where m age>10";
String jpqls = "select t From Team t join fetch t.members m join fetch Order o";
fetch join
은 엔티티에 직접 적용하는 글로벌 로딩 전략
보다 우선함//1
String jpql = "select distinct t from Team t join fetch t.members";
List<Team> teamList = em.createQuery(jpql, Team.class)
.setFirstResult(0)
.setMaxResults(1)
.getResultList();
//일대다 페치조인 주의할 점
//- 둘 이상의 컬렉션은 페치조인할 수 없다.
//- 컬렉션을 페치조인하면 데이터 뻥튀기.. 한번에 데이터 조회후,
// 메모리에서 페이징 API가 적용되므로 경고로그가 나온다.(매우 위험)
//+ 별칭 사용 불가
String jpqls = "select t From Team t join fetch t.members m where m.age>10";
//1의 대안 : 2
String jpqlDv = "select m from Member m join fetch m.team t";
// 위 일대다 페치조인 페이징 API의 대안 : 다대일 페치조인으로 뒤집어서 페이징 api 적용
//1의 대안 : 3
String jpqlDv2 = "select t from Team t"; //+ BatchSize(size = 100)정도
//팀A에 대한 사람이 2명이면, 총 3개의 쿼리가 나간다.
//Team에 대한 쿼리1(LAZY 로딩), Member에 대한 쿼리2개
//잘못하면 N+1문제 발생
//따라서 그에 대한 해결방법 : 1) 페이징 처리(똑같이 n+1문제 발생) 2) BatchSize설정
List<Team> teamList = em.createQuery(jpqlDv2, Team.class)
.getResultList();
for(Team team : teamList) {
System.out.println("teamname = " + team.getName() + ", team = " + team);
for (Member member : team.getMembers()) {
//페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println("-> username = " + member.getUsername()+ ", member = " + member);
}
}
Hibernate:
/* select
t
from
Team t */ select
team0_.id as id1_3_,
team0_.name as name2_3_
from
Team team0_
teamname = 팀A, team = hellojpa.Team@40021799
Hibernate:
select
members0_.TEAM_ID as TEAM_ID5_0_0_,
members0_.id as id1_0_0_,
members0_.id as id1_0_1_,
members0_.age as age2_0_1_,
members0_.TEAM_ID as TEAM_ID5_0_1_,
members0_.type as type3_0_1_,
members0_.username as username4_0_1_
from
Member members0_
where
members0_.TEAM_ID=?
-> username = 회원1, member = Member{id=58, username='회원1', age=20}
-> username = 회원2, member = Member{id=59, username='회원2', age=20}
teamname = 팀B, team = hellojpa.Team@1b32cd16
Hibernate:
select
members0_.TEAM_ID as TEAM_ID5_0_0_,
members0_.id as id1_0_0_,
members0_.id as id1_0_1_,
members0_.age as age2_0_1_,
members0_.TEAM_ID as TEAM_ID5_0_1_,
members0_.type as type3_0_1_,
members0_.username as username4_0_1_
from
Member members0_
where
members0_.TEAM_ID=?
총 3번의 쿼리가 나간다.
Hibernate:
/* select
t
from
Team t */ select
team0_.id as id1_3_,
team0_.name as name2_3_
from
Team team0_
teamname = 팀A, team = hellojpa.Team@588ffeb
Hibernate:
/* load one-to-many hellojpa.Team.members */ select
members0_.TEAM_ID as TEAM_ID5_0_1_,
members0_.id as id1_0_1_,
members0_.id as id1_0_0_,
members0_.age as age2_0_0_,
members0_.TEAM_ID as TEAM_ID5_0_0_,
members0_.type as type3_0_0_,
members0_.username as username4_0_0_
from
Member members0_
where
members0_.TEAM_ID in (
?, ?
)
-> username = 회원1, member = Member{id=63, username='회원1', age=20}
-> username = 회원2, member = Member{id=64, username='회원2', age=20}
teamname = 팀B, team = hellojpa.Team@67ec8477
-> username = 회원3, member = Member{id=65, username='회원3', age=20}
1번의 쿼리가 나간다.
[JPQL]
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", memberId)
.getResultList();
실행되는 SQL은 같다.
select m.* from Member m where m.id=?
[JPQL]
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=?
@Query
로 모두 캐싱해두고 쿼리를 검증하는 기능까지 있어서 훨씬 유용@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();
이렇게 사용하면 된다.
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setAge(20);
member1.setMemberType(MemberType.ADMIN);
member1.changeTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setAge(20);
member2.setMemberType(MemberType.ADMIN);
member2.changeTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setAge(20);
member3.setMemberType(MemberType.ADMIN);
member3.changeTeam(teamB);
em.persist(member3);
//이 사이에서 flush가 자동으로 일어난다,
//(flush 자동으로 일어나는 조건 : commit되기 전 or 쿼리가 실행되기 전)
//db에 직접 벌크연산(한번에 update or delete 연산이 나가는 것) 발생
//벌크연산일때는 영속성 컨텍스트에 반영되지 않음
int resultCount = em.createQuery("update Member m set m.age = 20")
.executeUpdate();
System.out.println("resultCount = "+resultCount); //3 출력
//영속성 컨텍스트에는 반영이 안되어있음(clear을 해야 데이터가 날아가는데
//flush한다고 해서 데이터가 없어지는 게 아님)
Member findMember = em.find(Member.class, member1.getId()); //0출력
em.clear();
Member findMember = em.find(Member.class, member1.getId()); //20출력
tx.commit();