HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
✍🏻
Learnary (Learn - Diary)
/
🌪️
Soloving_Issue_Log
/
JPA질의 Collection 참조 객체 주의사항

JPA질의 Collection 참조 객체 주의사항

생성
Jun 9, 2023 05:13 PM
kindOf
JPA
Status
Done

요약


컬렉션 참조 객체의 FetchJoin에서는 카테시안 곱이 발생하니 distinct, 또는 Set 컬렉션을 활용하라!

상황


하나의 학교에는 여러개의 과목들이 존재한다.
이런 경우 각 아카데미에 두개씩 과목을 넣어보겠다.FetchJoin 적용
결과는 1관계있는 아카데미가 매핑된 서브젝트 개수와 같아지게된다.
notion image
 

해결


2가지 방법이 있다.
  1. 1관계 있는 컬렉션 참조 유형을 List → Set으로 변경하기
  1. ditinct 키워드 사용하기
 
1:N쪽의 1의 관계에서 참조하는 컬렉션 객체를 보통 JPA의 FetchJoin으로 가져올 것이다.
카테시안곱이 발생한다는 사실을 염두해두고 1쪽의 객체가 자연스레 N개의 객체의 연관된 개수와 맞쳐지게 된다.
그러므로 1쪽의 Fetch Join은 사용한 쿼리는 Distinct 키워드를 작성해주거나 또는 Set 컬렉션 객체를 활용해야 한다.
@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; } }
@DataJpaTest 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)); } }
@OneToMany(cascade = CascadeType.ALL) @JoinColumn(name="academy_id") private Set<Subject> subjects = new LinkedHashSet<>();
@Query("select DISTINCT a from Academy a join fetch a.subjects s join fetch s.teacher") List<Academy> findAllWithTeacher(); @EntityGraph(attributePaths = {"subjects", "subjects.teacher"}) @Query("select DISTINCT a from Academy a") List<Academy> findAllEntityGraphWithTeacher();