스프링으로 개발을 하던 중, Entity와 Repository를 생성하고 특정 필드의 값을 가져오려고 하니 에러가 발생했다.
그 이유는 @OneToMany 어노테이션을 설정해둔 List 타입의 필드 때문이었다.
분명히 일반 @Column 이나 @ManyToOne 으로 설정해둔 필드들은 이상 없이 값들을 가져오는데, @OneToMany로 설정해둔 값들은 왜 가져오지 못하는걸까?
단순히 1:N 으로 여러 개의 엔티티와 연결되어 있기 때문일까?
이전부터 궁금했던 사항이기에 더 깊게 공부해보고자 했다.
@Getter
@Setter
@Entity
public class Question {
// 생략
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE)
private List<Answer> answerList;
}
// Question과 동일
public class Answer {
// 생략
@ManyToOne
private Question question;
}
Question@OneToMany : 한 질문에는 여러 답변이 달릴 수 있음cascade = CascadeType.REMOVE : 질문이 삭제되면 그에 딸린 값(해당 엔티티를 외래키로 가진)도 함께 삭제Answer@ManyToOne : 여러 개의 답글이 한 질문에 달릴 수 있음@Test
void getAnswerListTest() {
Question q = questionRepository.findById(2)
.orElseThrow(RunTimeException::new);
List<Answer> answerList = q.getAnswerList();
assertEquals(1, answerList.size());
assertEquals("내용", answerList.get(0).getContent());
}

1) QuestionRepository 에서 findById 메서드 호출
findById 메서드를 사용해 ID가 2인 데이터를 가져옴answerList 는 가져오지 않음 (이유는 후에 서술)2) DB와의 연결(세션)이 끊어짐
findById 를 사용한 후 DB와의 연결이 종료됨3) q.getAnswerList() 호출
Question 데이터의 필드인 answerList 를 가져오려고 시도❗ 이러한 에러가 발생한 이유는 지연 로딩이 발생했기 때문!!!
데이터를 가져오는 두 가지의 방법
@ManyToMany , @OneToMany 의 옵션인 fetch 의 기본 타입@OneToOne, @ManyToOne 의 옵션인 fetch 의 기본 타입- 위에서는 List` 를 받아올 때 문제가 발생Answer 엔티티의 필드 중 Question 필드는 @ManyToOne 이기 때문에 JPA는 일반 필드와 연관 데이터를 다르게 처리하기 때문
findById 로 Question 을 가져올 때, 일반 필드의 데이터들은 미리 가져옴answerList 는 Answer 엔티티와 연관된 데이터JPA 는 설정에 따라 이러한 데이터를 바로바로 가져오지 않음@OneToMany 나 @ManyToMany 의 기본 FetchType은 지연 로딩1) 효율성
List 는 여러 개의 값을 포함할 수 있음2) 필요성
title 값만 필요한데, 굳이 answerList의 값을 가져올 필요가 없음SELECT id FROM questionanswerList 의 경우 다른 테이블 answer 과 연관되어 있음Question 을 가져와도 List 의 Answer 데이터를 가져오려면 또 다른 쿼리를 실행해야 함1) 관련된 엔티티 필드의 로딩 전략을 즉시 로딩으로 수정
@Getter
@Setter
@Entity
public class Question {
// 생략
@OneToMany(mappedBy = "question", cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
private List<Answer> answerList;
}
// Question과 동일
public class Answer {
// 생략
@ManyToOne(fetch = FetchType.EAGER)
private Question question;
}
2) 사용되는 메서드에 @Transactional 어노테이션 사용
@Transactional
@Test
void getAnswerListTest() {
Question q = questionRepository.findById(2)
.orElseThrow(RunTimeException::new);
List<Answer> answerList = q.getAnswerList();
assertEquals(1, answerList.size());
assertEquals("내용", answerList.get(0).getContent());
}
Transaction 으로 만들어줌
- 사실 실제 서버에서 JPA 프로그램들을 실행할 때는 DB 세션이 종료되지 않기 때문에, 위와 같은 처리가 필요 없을 수 있음
| 특성 | Lazy Loading | Eager Loading |
|---|---|---|
| 데이터 로드 시점 | 필요한 순간 로드 | 처음부터 모든 데이터를 로드 |
| 성능 | 초기 로드가 가볍지만 필요 시 추가 쿼리 | 초기 로드가 무거울 수 있음 |
| 사용 사례 | 데이터가 많거나 자주 사용하지 않는 경우 | 연관 데이터를 항상 함께 사용하는 경우 |
참고) OpenAI. (2024).ChatGPT(4o)[Large language model].https://chatgpt.com/