framework/spring

MyBatis Mapper를 설계해보자(N+1문제 해결하기)

datajoy 2020. 5. 21. 02:22

N+1문제란?

조회 시 select 쿼리를 1번 날렸다가, 관련 컬럼을 얻기 위해 쿼리를 N번 추가 수행하는 문제로

1번 조회를 위해 N번 쿼리를 수행하기 때문에 데이터가 증가하게 될 수 록 성능저하를 가져오게된다.

Mabytis Mapper에서는 resultMap의 Nested select로 인해 발생하게 된다.

 

Nested select는 어떻게 N+1 문제를 발생시키나?

아래 Mapper를 보자

<resultMap id="empResultMap" type="com.test.domain.emp">
	<id property="seq" colunm="seq" />
    <result property="empNm" colunm="empNm" />
    <result property="orgId" colunm="orgId" />
    <collection property="orgList" ofType="org" column={org_id=org_id} select="selectOrg"/>
</resultMap>

<resultMap id="orgResultMap" type="com.test.domain.org">
    <id property="empId" colunm="orgId" />
    <result property="empNm" colunm="orgNm" />
</resultMap>

<select id="selectEmp" parameterType="hashmap" resultMap="empResultMap">
	SELECT * FROM EMP
</select>

<select id="selectOrg" parameterType="hashmap" resultMap="orgResultMap">
	SELECT * FROM ORG
    WHERE ORG_ID = #{orgId}
</select>

resultMap의 collection을 사용할때 select를 사용하게된다. 

이 때, selectEmp로 1번의 조회를 하려고하는데 collection의 select로 selectOrg를 조회하기 위해 N번 쿼리를 실행하게된다.

쿼리를 N번 실행하게되면 DB에 N번 접속하여 데이터를 가져오기 때문에 ORG 테이블의 데이터가 늘어날 수 록 성능저하게되게 된다.

 

해결은 어떻게하는가?

Mapper의 resultMap에서 Nested select를 사용하는 것이 아닌 Nested results 방식을 사용하는 것이다.

아래 Mapper를 보자

<resultMap id="empResultMap" type="com.test.domain.emp">
	<id property="seq" colunm="seq" />
    <result property="empNm" colunm="empNm" />
    <result property="empId" colunm="orgId" />
    <collection property="orgList" resultMap="orgResultMap"/>
</resultMap>

<resultMap id="orgResultMap" type="com.test.domain.org">
    <id property="empId" colunm="orgId" />
    <result property="empNm" colunm="orgNm" />
</resultMap>

<select id="selectEmp" parameterType="hashmap" resultMap="empResultMap">
	SELECT * 
    FROM EMP A
    LEFT OUTER JOIN ORG B ON A.ORG_ID = B.ORG_ID
</select>

Nested select와 달리 collection에서는 orgResultMap을 참조하고 EMP테이블과 ORG테이블 조인을 하여 한번에 쿼리를 수행하여 데이터를 가져온다.

 

그럼, Nested results 방식을 무조건 사용해야하나?

Nested results도 단점이 존재하며, Nested select는 사용을 지양하는 것이지 상황에 따라서 사용을 해야한다.

Spring에서 테이블간의 관계를 정의하는 방법에 대해 장단점을 분석하고 상황에 따라 적절하게 사용을 하는것이 좋다.

 

테이블간의 관계정의 방법은...

첫번째, Nested results 방식으로 테이블 간의 Join을 통해 관계정의.

두번째, Nested select 방식으로 독립적으로 Mapper를 구성하고 select 설정으로 각 Mapper간의 관계정의.

세번째, 독립적으로 Mapper를 구성하고 service layer에서 맵핑을 통해 관계정의.

 

Mapper를 어떻게하면 효율적으로 구성할수있을까?

Mapper를 효율적으로 구성하기위해선 본인만의 기준이 필요하다.

내 기준은 아래와 같다.

첫번째, 주체가 되는 도메인은 독립적은 mapper로 구성하고 association / collection으로 관계를 정의한다.

join으로 관리하면 관리포인트가 나뉘게되 관리가 어려워진다.

관계가 복잡해질경우 상위 관계의 데이터 건수가 고정적이지않다면 이 방법은 성능을 저하 시킬수있다. 예를들어, 상위 관계의 도메인이 계속적으로 증가하는 성향이라면 상위도메인건수 만큼 하위도메인을 db에서 호출해야하기때문에 성능을 저하시킨다.

두번째, 종속적인 관계의도메인은 query단에서 join을 통해 관계를 정의한다.

종속적 관계는 관리포인트가 나뉠 확률이 적기때문에 join을 통해 성능을 높인다.

 

마치며...

DB에서 가져와서 데이터를 도메인과 맵핑 시켜주는 Mapper의 설계는 매우 중요하다. 

데이터를 가져와서 맵핑하는 가장 초기단계이기때문에 Mapper의 설계를 잘못되면 web application의 전체설계를 망칠 수 있다. 

신중하게 방식을 결정하여 개발을 할 수 있도록하자.

 

참고자료

https://lyb1495.tistory.com/110