Skip to content

Commit

Permalink
#1 - 해결(1) - FetchJoin 사용
Browse files Browse the repository at this point in the history
  • Loading branch information
iseunghan committed Feb 3, 2024
1 parent 854e361 commit 66fe017
Show file tree
Hide file tree
Showing 4 changed files with 337 additions and 0 deletions.
217 changes: 217 additions & 0 deletions jpa-lab/Member_Lazy_Team_Lazy_Fetch_Join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
## Abstract
**Team - Member 연관관계**
- Team의 member는 OneToMany(LAZY) 전략 사용
- Member의 Team은 ManyToOne(EAGER) 전략 사용

## Domain
### Team
```java
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Team {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Member> members = new ArrayList<>();

@Builder
private Team(String name) {
this.name = name;
}

public void addMember(Member member) {
this.members.add(member);
member.updateTeam(this);
}
}


```

### Member
```java
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;

@Builder
private Member(String name) {
this.name = name;
}

public void updateTeam(Team team) {
this.team = team;
}
}

```

### Repository
각 Repository에 Fetch Join을 추가
```java
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query(value = "select m from Member m join fetch m.team")
List<Member> findMembersFetchJoin();
}
```
```java
public interface TeamRepository extends JpaRepository<Team, Long> {
Optional<Team> findTeamByName(String name);
@Query(value = "select t from Team t join fetch t.members where t.name = :name")
Optional<Team> findTeamByNameFetchJoin(String name);
@Query(value = "select t from Team t join fetch t.members")
List<Team> findTeamsFetchJoin();
}
```

## Test
[Member_Eager_Team_Lazy.md](Member_Eager_Team_Lazy.md)의 setup, clear, clearPersistenceContext 메소드 동일
```java
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class Member_Lazy_Team_Lazy_Fetch_Join_Test {
@Autowired private MemberRepository memberRepository;
@Autowired private TeamRepository teamRepository;
@PersistenceContext private EntityManager em;

// setup, clear, clearPersistenceContext 메소드 동일

@DisplayName("단일 팀을 조회하고, 멤버를 사용하던 안하던 -> 1개의 쿼리가 나간다.(팀 조회하는 쿼리 1개)")
@Test
void find_One_Team_test() {
clearPersistenceContext();

System.out.println("----------find_One_Team_test start-----------");
Team team = teamRepository.findTeamByNameFetchJoin("team1")
.orElseThrow(RuntimeException::new);
assertThat(team.getName()).isEqualTo("team1");
System.out.println("----------find_One_Team_test end-----------");
}

@DisplayName("모든 팀을 조회하고, 지연로딩 된 멤버를 사용할 때 -> 단 1개의 쿼리만 나간다")
@Test
void team_findAll_test() {
clearPersistenceContext();

System.out.println("----------team_findAll_test start-----------");
List<Team> teamList = teamRepository.findTeamsFetchJoin();
assertThat(teamList).hasSize(3);
System.out.println("----------team_findAll_test mid-----------");
teamList.stream()
.map(Team::getMembers)
.map(List::stream)
.forEach(memberStream -> memberStream
.map(Member::getName)
.forEach(System.out::println)
);
System.out.println("----------team_findAll_test end-----------");
}

@DisplayName("모든 멤버를 조회하고, 지연로딩 된 팀을 사용할 때 -> 단 1개의 쿼리만 나간다")
@Test
void member_findAll_test() {
clearPersistenceContext();

System.out.println("----------member_findAll_test start-----------");
List<Member> memberList = memberRepository.findMembersFetchJoin();
assertThat(memberList).hasSize(6);
System.out.println("----------member_findAll_test mid-----------");
memberList.stream()
.map(Member::getTeam)
.map(Team::getName)
.forEach(System.out::println);
System.out.println("----------member_findAll_test end-----------");
}
}


```

## Console output

### find_One_Team_test()
**중요 로그:**
Fetch Join으로 가져왔으므로 N+1 해결은 member를 사용하던 안하던 쿼리가 발생하지 않는다.
```console
----------find_One_Team_test start-----------
Hibernate:
select
t1_0.id,
m1_0.team_id,
m1_0.id,
m1_0.name,
t1_0.name
from
team t1_0
join
member m1_0
on t1_0.id=m1_0.team_id
where
t1_0.name=?
----------find_One_Team_test end-----------
```

### team_findAll_test()
**중요 로그:**
Fetch Join으로 가져왔으므로 N+1 해결은 물론 사용하는 시점에 쿼리가 발생하지 않는다.
```console
----------team_findAll_test start-----------
Hibernate:
select
t1_0.id,
m1_0.team_id,
m1_0.id,
m1_0.name,
t1_0.name
from
team t1_0
join
member m1_0
on t1_0.id=m1_0.team_id
----------team_findAll_test mid-----------
member1-1
member1-2
member2-1
member2-2
member3-1
member3-2
----------team_findAll_test end-----------
```

### member_findAll_test()
**중요 로그:**
member를 조회할 때 team을 fetch Join으로 불러왔기 때문에 inner join을 사용해서 한방에 가져왔다.
```console
----------member_findAll_test start-----------
Hibernate:
select
m1_0.id,
m1_0.name,
t1_0.id,
t1_0.name
from
member m1_0
join
team t1_0
on t1_0.id=m1_0.team_id
----------member_findAll_test mid-----------
team1
team1
team2
team2
team3
team3
----------member_findAll_test end-----------
```

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

import me.iseunghan.jpalab.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

public interface MemberRepository extends JpaRepository<Member, Long> {
@Query(value = "select m from Member m join fetch m.team")
List<Member> findMembersFetchJoin();
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

import me.iseunghan.jpalab.entity.Team;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.List;
import java.util.Optional;

public interface TeamRepository extends JpaRepository<Team, Long> {
Optional<Team> findTeamByName(String name);
@Query(value = "select t from Team t join fetch t.members where t.name = :name")
Optional<Team> findTeamByNameFetchJoin(String name);
@Query(value = "select t from Team t join fetch t.members")
List<Team> findTeamsFetchJoin();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package me.iseunghan.jpalab.repository;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import me.iseunghan.jpalab.entity.Member;
import me.iseunghan.jpalab.entity.Team;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class Member_Lazy_Team_Lazy_Fetch_Join_Test {
@Autowired private MemberRepository memberRepository;
@Autowired private TeamRepository teamRepository;
@PersistenceContext private EntityManager em;

@BeforeEach
void setup() {
System.out.println("----------setup start-----------");
Team team1 = Team.builder().name("team1").build();
team1.addMember(Member.builder().name("member1-1").build());
team1.addMember(Member.builder().name("member1-2").build());

Team team2 = Team.builder().name("team2").build();
team2.addMember(Member.builder().name("member2-1").build());
team2.addMember(Member.builder().name("member2-2").build());

Team team3 = Team.builder().name("team3").build();
team3.addMember(Member.builder().name("member3-1").build());
team3.addMember(Member.builder().name("member3-2").build());

teamRepository.save(team1);
teamRepository.save(team2);
teamRepository.save(team3);

clearPersistenceContext();
System.out.println("----------setup end-----------");
}

@AfterEach
void clear() {
System.out.println("----------clear start-----------");
memberRepository.deleteAll();
teamRepository.deleteAll();
clearPersistenceContext();
System.out.println("----------clear end-----------");
}

private void clearPersistenceContext() {
em.flush();
em.clear();
}

@DisplayName("단일 팀을 조회하고, 멤버를 사용하던 안하던 -> 1개의 쿼리가 나간다.(팀 조회하는 쿼리 1개)")
@Test
void find_One_Team_test() {
clearPersistenceContext();

System.out.println("----------find_One_Team_test start-----------");
Team team = teamRepository.findTeamByNameFetchJoin("team1")
.orElseThrow(RuntimeException::new);
assertThat(team.getName()).isEqualTo("team1");
System.out.println("----------find_One_Team_test end-----------");
}

@DisplayName("모든 팀을 조회하고, 지연로딩 된 멤버를 사용할 때 -> 단 1개의 쿼리만 나간다")
@Test
void team_findAll_test() {
clearPersistenceContext();

System.out.println("----------team_findAll_test start-----------");
List<Team> teamList = teamRepository.findTeamsFetchJoin();
assertThat(teamList).hasSize(3);
System.out.println("----------team_findAll_test mid-----------");
teamList.stream()
.map(Team::getMembers)
.map(List::stream)
.forEach(memberStream -> memberStream
.map(Member::getName)
.forEach(System.out::println)
);
System.out.println("----------team_findAll_test end-----------");
}

@DisplayName("모든 멤버를 조회하고, 지연로딩 된 팀을 사용할 때 -> 단 1개의 쿼리만 나간다")
@Test
void member_findAll_test() {
clearPersistenceContext();

System.out.println("----------member_findAll_test start-----------");
List<Member> memberList = memberRepository.findMembersFetchJoin();
assertThat(memberList).hasSize(6);
System.out.println("----------member_findAll_test mid-----------");
memberList.stream()
.map(Member::getTeam)
.map(Team::getName)
.forEach(System.out::println);
System.out.println("----------member_findAll_test end-----------");
}
}

0 comments on commit 66fe017

Please sign in to comment.