MyBatis는 Java 개발자들이 SQL 쿼리와 데이터베이스를 효과적으로 다룰 수 있게 도와주는 ORM(Object-Relational Mapping) 프레임워크입니다. 그러나 MyBatis를 사용하다 보면 N+1 쿼리 문제에 직면할 수 있습니다. 이 문제는 한 개의 쿼리로 조회하는 대신, N개의 추가 쿼리가 발생해 성능에 부정적인 영향을 줄 수 있습니다.
N+1 쿼리 문제란?
N+1 쿼리 문제는 다음과 같은 상황에서 발생합니다.
- 한 개의 쿼리로 N개의 엔티티를 조회합니다.
- 각 엔티티의 연관된 엔티티를 조회하기 위해 추가적인 N개의 쿼리가 실행됩니다.
이렇게 되면 원래의 한 개의 쿼리 대신 N+1개의 쿼리가 실행되므로 성능 저하의 원인이 됩니다.
N+1 쿼리 문제 해결 방법
N+1 쿼리 문제를 해결하는 방법은 크게 두 가지입니다.
1. 페치 조인 사용하기
페치 조인(fetch join)은 MyBatis에서 제공하는 기능으로, 연관된 엔티티를 함께 조회합니다. 이렇게 함으로써 추가적인 쿼리 없이 한 번의 쿼리로 모든 데이터를 조회할 수 있습니다.
@Select("SELECT * FROM orders o JOIN customers c ON o.customer_id = c.id")
@Results({
@Result(property = "id", column = "id")
@Result(property = "customer", column = "customer_id",
one = @One(select = "com.example.CustomerMapper.findById"))
})
List<Order> findAll();
위의 예시에서는 orders
테이블과 customers
테이블을 연관시키는 페치 조인을 사용하여 한 번의 쿼리로 모든 주문과 주문 고객을 함께 조회합니다.
2. 지연 로딩 사용하기
지연 로딩(lazy loading)은 연관된 엔티티를 실제로 사용할 때까지 로딩을 미루는 방법입니다. 이를 통해 초기 쿼리 수행 시 N+1 쿼리 문제를 해결할 수 있습니다. 다만, 실제로 데이터를 사용할 때 연관된 엔티티를 조회하는 추가적인 쿼리가 실행됩니다.
@Select("SELECT * FROM customers")
List<Customer> findAll();
@Select("SELECT * FROM orders WHERE customer_id = #{customerId}")
List<Order> findOrdersByCustomerId(int customerId);
위의 예시에서는 findAll
메소드로 모든 고객을 조회할 때는 주문 정보를 로딩하지 않습니다. 대신, 실제로 주문 정보가 필요한 경우에는 findOrdersByCustomerId
메소드를 호출하여 연관된 주문 정보를 조회합니다.
결론
MyBatis를 사용할 때 N+1 쿼리 문제에 직면할 수 있습니다. 이 문제를 해결하기 위해 페치 조인과 지연 로딩을 적절히 사용하여 성능 저하를 방지할 수 있습니다. 이를 통해 MyBatis를 더욱 효과적으로 활용할 수 있습니다.
참고 자료: