week 5 junghyunlyoo n 1 - GANGNAM-JAVA/JAVA-STUDY GitHub Wiki
A ์ํฐํฐ์ B ์ํฐํฐ๊ฐ 1:N ๊ด๊ณ์ผ ๋, 1๊ฐ์ A ์ํฐํฐ๋ฅผ ์กฐํํ ๋๋ง๋ค
A์ ์ฐธ์กฐ๋๊ณ ์๋ N๊ฐ์ B ์ํฐํฐ๊ฐ N๊ฐ์ ๋ ๋ฆฝ์ ์ธ ์ฟผ๋ฆฌ๋ฅผ ํตํด ์กฐํ๋๋ ํ์
A ์ํฐํฐ์ A ์ํฐํฐ๋ฅผ ์ฐธ์กฐํ๊ณ ์๋ N๊ฐ์ B ์ํฐํฐ ๋ชจ๋๋ฅผ 1๊ฐ์ ์ฟผ๋ฆฌ๋ก๋ ์กฐํํ ์ ์๋๋ฐ,
JPA ์ํฐํฐ์ fetch ์ ๋ต์ LAZY๋ก ์ค์ ํ๋ฉด N+1๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ํตํด ์กฐํ๋๋ค.
(A ์ํฐํฐ ์กฐํ 1๋ฒ + N๊ฐ์ B ์ํฐํฐ LAZY ์กฐํ N๋ฒ)
N๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ๋ ์คํ๋๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ ์กฐํ ์๋๊ฐ ๋ฆ์ด์ง๊ฒ ๋๋ค.
@Entity
@Getter
@NoArgsConstructor
public class Academy {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name="academy_id")
private List<Subject> subjects = new ArrayList<>();
@Builder
public Academy(String name, List<Subject> subjects) {
this.name = name;
if(subjects != null){
this.subjects = subjects;
}
}
public void addSubject(Subject subject){
this.subjects.add(subject);
subject.updateAcademy(this);
}
}
@Entity
@Getter
@NoArgsConstructor
public class Subject {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "academy_id", foreignKey = @ForeignKey(name = "FK_SUBJECT_ACADEMY"))
private Academy academy;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "teacher_id", foreignKey = @ForeignKey(name = "FK_SUBJECT_TEACHER"))
private Teacher teacher;
@Builder
public Subject(String name, Academy academy, Teacher teacher) {
this.name = name;
this.academy = academy;
this.teacher = teacher;
}
public void updateAcademy(Academy academy){
this.academy = academy;
}
}
@Entity
@Getter
@NoArgsConstructor
public class Teacher {
@Id
@GeneratedValue
private Long id;
private String name;
public Teacher(String name) {
this.name = name;
}
}
@Service
@Slf4j
public class AcademyService {
private AcademyRepository academyRepository;
public AcademyService(AcademyRepository academyRepository) {
this.academyRepository = academyRepository;
}
@Transactional(readOnly = true)
public List<String> findAllSubjectNames(){
return extractSubjectNames(academyRepository.findAll());
}
/**
* Lazy Load๋ฅผ ์ํํ๊ธฐ ์ํด ๋ฉ์๋๋ฅผ ๋ณ๋๋ก ์์ฑ
*/
private List<String> extractSubjectNames(List<Academy> academies){
log.info(">>>>>>>>[๋ชจ๋ ๊ณผ๋ชฉ์ ์ถ์ถํ๋ค]<<<<<<<<<");
log.info("Academy Size : {}", academies.size());
return academies.stream()
.map(a -> a.getSubjects().get(0).getName())
.collect(Collectors.toList());
}
}
์ฌ๊ธฐ์ ์๋น์ค์ findAllSubjectNames๋ฅผ ํธ์ถํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น? ์๋ ์ฝ๋์์ ํ ์คํธํด๋ณด์
@RunWith(SpringRunner.class)
@SpringBootTest
public class AcademyServiceTest {
@Autowired
private AcademyRepository academyRepository;
@Autowired
private AcademyService academyService;
@After
public void cleanAll() {
academyRepository.deleteAll();
}
@Before
public void setup() {
List<Academy> academies = new ArrayList<>();
for(int i=0;i<10;i++){
Academy academy = Academy.builder()
.name("๊ฐ๋จ์ค์ฟจ"+i)
.build();
academy.addSubject(Subject.builder().name("์๋ฐ์น๊ฐ๋ฐ" + i).build());
academies.add(academy);
}
academyRepository.save(academies);
}
@Test
public void Academy์ฌ๋ฌ๊ฐ๋ฅผ_์กฐํ์_Subject๊ฐ_N1_์ฟผ๋ฆฌ๊ฐ๋ฐ์ํ๋ค() throws Exception {
//given
List<String> subjectNames = academyService.findAllSubjectNames();
//then
assertThat(subjectNames.size(), is(10));
}
}
academy ์ ์ฒด๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ์
๊ฐ๊ฐ์ Academy๊ฐ ๋ณธ์ธ๋ค์ subject๋ฅผ ์กฐํํ๋ ์ฟผ๋ฆฌ 10๊ฐ๊ฐ ๋ฐ์ํ ๊ฒ์ ํ์ธํ ์ ์๋ค.
1๋ฒ์ ์ฟผ๋ฆฌ๋ก๋ ์กฐํ ๊ฐ๋ฅํ ๋ฐ์ดํฐ๊ฐ N+1๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ ํตํด ์กฐํ๋๋ค.
๋จ 1๋ฒ์ ์ฟผ๋ฆฌ๋ฅผ ํตํด ์กฐํํ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น?
Join Fetch๋ ํ์ ๋ฐ์ดํฐ๋ฅผ Lazy ๋ฐฉ์์ผ๋ก ์กฐํํ์ง ์๊ณ Eager ๋ฐฉ์์ผ๋ก ์กฐํํ๋ค.
๋จผ์ AcademyRepository์ Join Fetch๋ฅผ ์ ์ฉ์ํจ ๋ฉ์๋๋ฅผ ๋ง๋ ๋ค.
@Query("select a from Academy a join fetch a.subjects")
List<Academy> findAllJoinFetch();
๋ง์ฝ Subject์ ํ์ Entity ๊น์ง๋ ํ๋ฒ์ ์กฐํํ๊ณ ์ถ๋ค๋ฉด
์๋์ ๊ฐ์ด Join Fecth๋ฅผ ํ๋ฒ ๋ ์ถ๊ฐํ๋ค.
@Query("select a from Academy a join fetch a.subjects s join fetch s.teacher")
List<Academy> findAllJoinFetch();
์ด๋ ๊ฒ Join Fetch๋ฅผ ์ฌ์ฉํ๋ฉด N+1๊ฐ์ ์ฟผ๋ฆฌ๋ฅผ 1๊ฐ์ ์ฟผ๋ฆฌ๋ก ๋์ฒดํ ์ ์๋ค.
ํํธ Join Fetch๋ ํ๋๋ณ๋ก Eager, Lazy ๋ฐฉ์์ ์ฟผ๋ฆฌ์์ ํํํ๊ธฐ ๋๋ฌธ์ ์ฝ๋ ์์ ์ ๋ํ ๋ถ๋ด์ด ์๋ค.
Spring JPA 2.1๋ถํฐ ์ง์ํ๊ธฐ ์์ํ ์ด๋ ธํ ์ด์ ์ด๋ค.
@EntityGraph์ attributePaths์ ์ฟผ๋ฆฌ ์ํ์ ์ฆ์ ์กฐํํ ํ๋๋ช ์ ์ง์ ํ๋ฉด Lazy๊ฐ ์๋ Eager๋ก ์กฐํ๋ฅผ ํ๊ฒ ๋๋ค.
@EntityGraph(attributePaths = "subjects")
@Query("select a from Academy a")
List<Academy> findAllEntityGraph();
@EntityGraph์ ์ฌ์ฉํ๋ฉด Join Fetch ๋ฐฉ์์ ๋จ์ ์ด์๋ ์ฝ๋ ์์ ์ ๋ํ ๋ถ๋ด์ ์ด๋์ ๋ ํด๊ฒฐํ ์ ์๋ค.
์๋ณธ ์ฟผ๋ฆฌ์ธ select a from Academy a ๋ฅผ ์์ ํ์ง ์๊ณ Eager/Lazy ํ๋๋ฅผ ์ ์ํ ์ ์๋ค.
Subject์ ํ์ Entity ๊น์ง๋ Eager ๋ฐฉ์์ผ๋ก ์กฐํํ๊ณ ์ถ๋ค๋ฉด attributePaths๋ฅผ ํ๋ ๋ ์ง์ ํ๋ค.
@EntityGraph(attributePaths = {"subjects", "subjects.teacher"})
@Query("select a from Academy a")
List<Academy> findAllEntityGraph();
Join Fetch์ @EntityGraph๋ ๋ชจ๋ ์นดํ ์์ ๊ณฑ(Cartesian Product)์ด ๋ฐ์ํ๋ค.
๋ฐ๋ผ์ Subject์ ์ ๋งํผ Academy๊ฐ ์ค๋ณต ์์ฑ๋๊ณ ์ค๋ณต ์์ฑ๋ Academy๋ค ๋ชจ๋๊ฐ ๋ฆฌ์คํธ์ ์ ์ฅ๋๋ค.
์ค๋ณต์ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ 2๊ฐ์ง๊ฐ ์๋ค.
- ์ ์ด์ ์ค๋ณตํด์ ์กฐํํ์ง ์๋๋ก Query select์ distinct๋ฅผ ์ฌ์ฉํ๋ค.
Join Fetch
@Query("select DISTINCT a from Academy a join fetch a.subjects s join fetch s.teacher")
List<Academy> findAllWithTeacher();
@EntityGraph
@EntityGraph(attributePaths = {"subjects", "subjects.teacher"})
@Query("select DISTINCT a from Academy a")
List<Academy> findAllEntityGraphWithTeacher();
- ์กฐํ๋ ๋ฐ์ดํฐ๋ฅผ List๊ฐ ์๋ Set์ ์ ์ฅํ์ฌ ์ค๋ณต๋์ง ์๊ฒ ์ ์ฅํ๋ค.
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name="academy_id")
private Set<Subject> subjects = new LinkedHashSet<>();
//LinkedHashSet์ ์ฌ์ฉํ์ฌ ์์๋ฅผ ๋ณด์ฅ
์ฆ์ ๋ก๋ฉ(EAGER)
- OneToOne
- ManyToOne
์ง์ฐ ๋ก๋ฉ(LAZY)
- OneToMany
- ManyToMany