OneToMany
나 ManyToMany
같은 다대일, 일대다 관계를 조회할 때 발생예를들어, Customer 와 Order가 일대다 관계를 가지고 있다고 가정
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
}
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String product;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "customer_id")
private Customer customer;
}
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}
@Service
@RequiredArgsConstructor
public class CustomerService {
private final CustomerRepository customerRepository;
public List<Customer> getAllCustomersWithOrders() {
List<Customer> customers = customerRepository.findAll(); // 1. 모든 고객을 조회
for (Customer customer : customers) {
System.out.println(customer.getOrders().size()); // 2. 각 고객의 주문 수를 출력
}
return customers;
}
}
customerRepository.findAll();
은 모든 고객을 조회하는 SQL 쿼리를 한 번 실행SELECT * FROM customer;
customer.getOrder()
를 호출할 때마다 해당 고객의 주문을 조회하는 추가적인 SQL 쿼리가 실행SELECT * FROM orders WHERE customer_id = ?;
만약 DB에 10명의 고객이 있다면, 총 11개의 SQL 쿼리 실행 (고객 조회 1번 + 주문 조회 10번)
고객 수가 증가할수록 쿼리 수도 증가하게 되어, 성능에 큰 영향을 미친다.
fetchJoin()
을 활용하면 고객과 주믄을 한 번의 쿼리로 가져올 수 있다.import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class CustomerRepositoryImpl implements CustomerRepositoryCustom {
private final JPAQueryFactory queryFactory;
public CustomerRepositoryImpl(JPAQueryFactory queryFactory) {
this.queryFactory = queryFactory;
}
@Override
public List<Customer> findAllWithOrders() {
QCustomer customer = QCustomer.customer;
QOrder order = QOrder.order;
return queryFactory
.selectFrom(customer)
.leftJoin(customer.orders, order).fetchJoin() // 페치 조인
.fetch();
}
}
대량의 연관 데이터를 처리할 때, @BatchSize
를 설정하여, 지정된 크기만큼의 데이터를 한 번에 가져오도록 설정
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY)
@BatchSize(size = 10)
private List<Order> orders = new ArrayList<>();
// Getters and Setters
}
@EntityGraph
를 사용하면 특정 쿼리에서 관련 엔티티를 즉시 로딩 (Eager Loading)으로 가져올 수 있다.
public interface CustomerRepository extends JpaRepository<Customer, Long>, QuerydslPredicateExecutor<Customer> {
@EntityGraph(attributePaths = {"orders"})
List<Customer> findAll(Predicate predicate);
}
Spring Data JPA에서 N+1 문제는 연관된 엔티티 조회 시 발생할 수 있는 일반적인 문제이며, 성능 저하가 발생할 수 있다.
이를 해결하기 위해 FETCH JOIN
, @EntityGraph
, @BatchSize
등을 적절히 사용해 쿼리 수를 줄이고, 성능을 최적화하는 것이 중요하다.
미리 N+1 문제를 인지하고 해결하는 것이 애플리케이션 성능을 유지하는 데 큰 도움이 된다.