Skip to content

Commit

Permalink
#1 - Member.team (Lazy), Team.members (Lazy) 전략일 때
Browse files Browse the repository at this point in the history
  • Loading branch information
iseunghan committed Feb 3, 2024
1 parent 8876410 commit 854e361
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 1 deletion.
139 changes: 139 additions & 0 deletions jpa-lab/Member_Lazy_Team_Lazy.md
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-----------
```
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class Member {
private Long id;
private String name;

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

Expand Down
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-----------");
}
}

0 comments on commit 854e361

Please sign in to comment.