@ManyToOne은 JPA에서 다대일(N:1) 관계를 매핑할 때 사용하는 어노테이션이다.
여기서 fetch = FetchType.LAZY는 지연 로딩(Lazy Loading) 전략을 의미한다.
@MayToOne의 기본값// 주문(Order)과 회원(Member)의 다대일 관계
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
private String orderNumber;
// LAZY: Member를 실제 사용할 때까지 조회하지 않음
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
}
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
}
Order order = orderRepository.findById(1L);
// SQL: SELECT * FROM orders o
// JOIN members m ON o.member_id = m.id
// WHERE o.id = 1
// → Order와 Member를 한 번에 조회 (JOIN 쿼리)
Order order = orderRepository.findById(1L);
// SQL: SELECT * FROM orders WHERE id = 1
// → Order만 조회
String memberName = order.getMember().getName();
// SQL: SELECT * FROM members WHERE id = ?
// → Member를 실제 사용할 때 추가 쿼리 실행 (프록시 객체 초기화)

List<Order> orders = orderRepository.findAll();
// SQL: SELECT * FROM orders (1번)
for (Order order : orders) {
String name = order.getMember().getName();
// SQL: SELECT * FROM members WHERE id = ? (N번)
}
// → 총 N+1번의 쿼리 실행!
필요할 때 명시적으로 가져오는 방법을 사용한다
// Fetch Join 사용
@Query("SELECT o FROM Order o JOIN FETCH o.member")
List<Order> findAllWithMember();
// EntityGraph 사용
@EntityGraph(attributePaths = {"member"})
List<Order> findAll();
@Transactional
public Order getOrder(Long id) {
return orderRepository.findById(id);
}
// 트랜잭션 밖에서 사용
Order order = orderService.getOrder(1L);
order.getMember().getName(); // ❌ 예외 발생!
// → 영속성 컨텍스트가 닫혀서 프록시 초기화 불가
@Transactional 범위를 조정JPA 공식 문서에는 모든 연관관계를 LAZY로 설정하고, 필요한 경우에만 fetch join으로 최적화하는 것을 권장하고 있다고 한다. @ManyToOne의 기본값은 EAGER이므로 명시적으로 LAZY를 설정하고 사용하자!