[java] MyBatis와 N+1 쿼리 문제 처리하기

N+1 쿼리란?

N+1 쿼리는 ORM(Object-Relational Mapping) 프레임워크에서 발생하는 성능 문제 중 하나입니다. 일반적으로 ORM은 객체와 데이터베이스 간의 매핑을 담당하고, 데이터베이스에서 필요한 데이터를 가져올 때에는 게으른 로딩(Lazy Loading)을 사용합니다. 하지만, 이로 인해 하나의 쿼리를 실행하기 위해 추가로 N개의 쿼리가 실행되는 상황이 발생하게 됩니다. 이를 N+1 쿼리라고 합니다.

MyBatis와 N+1 쿼리

MyBatis는 ORM 프레임워크 중 하나로, SQL 매퍼를 통해 데이터베이스와의 연동을 수행합니다. MyBatis에서도 N+1 쿼리 문제가 발생할 수 있지만, 이를 해결하기 위한 몇 가지 방법이 있습니다.

1. FetchType 설정 변경

MyBatis의 select 태그에서는 fetchType 속성을 이용하여 로딩 전략을 설정할 수 있습니다. 기본값인 lazy 대신 eager로 설정하면, 연관된 데이터도 함께 로딩되어 추가 쿼리 호출을 줄일 수 있습니다. 하지만, 모든 연관 객체에 대해 eager 로딩을 설정하면 데이터베이스 서버의 부하가 증가할 수 있으므로 주의해야 합니다.

<collection property="items" ofType="Item" fetchType="eager">
    ...
</collection>

2. join fetch 사용

MyBatis에서는 join fetch를 사용하여 즉시 로딩을 수행할 수 있습니다. 이를 이용하면 한 번의 쿼리로 모든 필요한 데이터를 가져올 수 있으므로 N+1 쿼리 문제를 해결할 수 있습니다.

<select id="selectOrderWithItems" resultMap="orderWithItemsResultMap">
    SELECT o.id, o.order_date, i.id AS item_id, i.name AS item_name
    FROM orders o 
    JOIN items i ON o.id = i.order_id
</select>

3. select 문을 한 번에 처리

N+1 쿼리 문제는 일반적으로 부모-자식 관계에서 발생합니다. 이를 해결하기 위해 MyBatis에서는 select 문을 한 번에 처리할 수 있는 방법을 제공합니다. collection 태그를 사용하여 한 번의 쿼리로 부모 객체와 연관된 모든 자식 객체를 가져올 수 있습니다.

<select id="selectOrdersWithItems" resultMap="orderResultMap">
    SELECT o.id, o.order_date, i.id AS item_id, i.name AS item_name
    FROM orders o 
    LEFT JOIN items i ON o.id = i.order_id
</select>

결론

MyBatis를 사용하는 프로젝트에서 N+1 쿼리 문제가 발생한다면, 위의 방법들을 적절히 활용하여 성능을 향상시킬 수 있습니다. 적절한 로딩 전략 설정 및 join fetch를 사용하여 최적화된 쿼리를 작성하는 것이 중요합니다.