-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#1 - Member.team (Lazy), Team.members (Lazy) 전략일 때
- Loading branch information
Showing
3 changed files
with
249 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
## 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; | ||
} | ||
} | ||
|
||
``` | ||
|
||
## Test | ||
[Member_Eager_Team_Lazy.md](Member_Eager_Team_Lazy.md)와 동일 (member_findAll_test의 DisplayName만 다름) | ||
```java | ||
@DataJpaTest | ||
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) | ||
public class Member_Lazy_Team_Lazy_Test { | ||
@Autowired private MemberRepository memberRepository; | ||
@Autowired private TeamRepository teamRepository; | ||
@PersistenceContext private EntityManager em; | ||
|
||
// setup, clear, clearPersistenceContext 메소드 동일 | ||
|
||
... | ||
|
||
@DisplayName("모든 멤버를 조회하고, 지연로딩 된 팀을 사용할 때 -> N+1이 발생한다.(모든 멤버 조회 1개, 각 팀을 조회하는 쿼리 3개)") // 이것만 다름! | ||
@Test | ||
void member_findAll_test() { | ||
clearPersistenceContext(); | ||
|
||
System.out.println("----------member_findAll_test start-----------"); | ||
List<Member> memberList = memberRepository.findAll(); | ||
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 | ||
### member_findAll_test() | ||
**중요 로그:** | ||
Member의 Team은 LAZY 전략으로 되어 있기 때문에 member를 조회하는 시점에는 member 조회 쿼리만 나가고, 실제 Team을 사용할 때 Team을 조회하는 쿼리가 나간다. | ||
실제 아래 로그에서도 `----------member_findAll_test mid-----------` 이후로 Team을 조회하는 쿼리가 나가는 것을 확인할 수 있다. | ||
```console | ||
----------member_findAll_test start----------- | ||
Hibernate: | ||
select | ||
m1_0.id, | ||
m1_0.name, | ||
m1_0.team_id | ||
from | ||
member m1_0 | ||
----------member_findAll_test mid----------- | ||
Hibernate: | ||
select | ||
t1_0.id, | ||
t1_0.name | ||
from | ||
team t1_0 | ||
where | ||
t1_0.id=? | ||
team1 | ||
team1 | ||
Hibernate: | ||
select | ||
t1_0.id, | ||
t1_0.name | ||
from | ||
team t1_0 | ||
where | ||
t1_0.id=? | ||
team2 | ||
team2 | ||
Hibernate: | ||
select | ||
t1_0.id, | ||
t1_0.name | ||
from | ||
team t1_0 | ||
where | ||
t1_0.id=? | ||
team3 | ||
team3 | ||
----------member_findAll_test end----------- | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
109 changes: 109 additions & 0 deletions
109
jpa-lab/src/test/java/me/iseunghan/jpalab/repository/Member_Lazy_Team_Lazy_Test.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_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.findTeamByName("team1") | ||
.orElseThrow(RuntimeException::new); | ||
assertThat(team.getName()).isEqualTo("team1"); | ||
System.out.println("----------find_One_Team_test end-----------"); | ||
} | ||
|
||
@DisplayName("모든 팀을 조회하고, 지연로딩 된 멤버를 사용할 때 -> N+1이 발생한다.(team1,2,3 조회하는 쿼리 1개, team1,2,3에 대한 멤버 조회하는 쿼리 3개)") | ||
@Test | ||
void team_findAll_test() { | ||
clearPersistenceContext(); | ||
|
||
System.out.println("----------team_findAll_test start-----------"); | ||
List<Team> teamList = teamRepository.findAll(); | ||
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("모든 멤버를 조회하고, 지연로딩 된 팀을 사용할 때 -> N+1이 발생한다.(모든 멤버 조회 1개, 각 팀을 조회하는 쿼리 3개)") | ||
@Test | ||
void member_findAll_test() { | ||
clearPersistenceContext(); | ||
|
||
System.out.println("----------member_findAll_test start-----------"); | ||
List<Member> memberList = memberRepository.findAll(); | ||
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-----------"); | ||
} | ||
} |