ResultMap 활용

ResultMap이란

myBatis에서 제공하는 자동 매핑으로 해결이 어려운 경우를 위해 구조를 설계할 수 있도록 만들어진 도구이다.

ResultMap이 필요한 경우

ResultMap은 다음과 같은 데이터 구조를 불러올 때 적합하다.

계층형 데이터 구조

객관식 시험과 관련된 정보들을 데이터베이스에 추가한다면 다음과 같은 구조를 가지게 된다.

  • 1번 시험문제

    • 1번 보기

    • 2번 보기

    • 3번 보기

    • 4번 보기

  • 2번 시험문제

    • 1번 보기

    • 2번 보기

    • 3번 보기

    • 4번 보기

시험이라는 상위 entity와 보기라는 하위 entity로 나누어 생각해본다면 다음과 같이 이해할 수 있다.

물리 테이블은 다음과 같이 구성될 것이다.

문제 테이블(quiz)

문제번호(no)

문제내용(text)

1

1번 문제

2

2번 문제

보기 테이블(choice)

보기번호(no)

보기내용(text)

문제번호(quiz)

1

1번 보기

1

2

2번 보기

1

3

3번 보기

1

4

4번 보기

1

5

1번 보기

2

6

2번 보기

2

7

3번 보기

2

8

4번 보기

2

위의 테이블 구조를 만약 join으로 불러온다면 어떻게 될까?

select 
    문제번호, 문제내용, 보기번호, 보기내용 
from
    문제 inner join 보기 on 문제.문제번호 = 보기.문제번호;

다음과 같은 결과가 나올 것이다.

문제번호

문제내용

보기번호

보기내용

1

1번 문제

1

1번 보기

1

1번 문제

2

2번 보기

1

1번 문제

3

3번 보기

1

1번 문제

4

4번 보기

2

2번 문제

5

1번 보기

2

2번 문제

6

2번 보기

2

2번 문제

7

3번 보기

2

2번 문제

8

4번 보기

위의 결과를 가지고 다음 화면을 출력할 수 있는지 따져봐야 한다.

join을 이용해서 위의 화면을 출력하는 것은 가능은 하지만 어렵다. 조건들이 많이 등장할 것이며, 코드는 지저분하게 표시된다.

다음 화면은 만들 수 있겠지만 우리가 원하는 화면은 아니다.

따라서 데이터를 화면과 같은 구조로 불러오고 싶은 경우 join 대신 ResultMap을 이용하면 좋다.

myBatis 설정

Spring myBatis 설정을 참고하여 마이바티스 설정을 마친다.

myBatis

ResultMap을 위한 클래스 구성

  • 1번 시험문제

    • 1번 보기

    • 2번 보기

    • 3번 보기

    • 4번 보기

  • 2번 시험문제

    • 1번 보기

    • 2번 보기

    • 3번 보기

    • 4번 보기

위와 같은 구조에서 시험 문제의 입장으로 바라본다면 다음과 같이 구성되어 있다고 생각할 수 있다.

  • 1번 시험문제

    • List<보기>

  • 2번 시험문제

    • List<보기>

따라서 시험 문제와 보기의 관점에서 각각 저장할 클래스를 만들어 myBatis가 구조를 이해할 수 있도록 구성한다.

class Choice : 보기 클래스

public class Choice{
    private int no;
    private String text;
}

보기 클래스는 Choice라는 이름으로 작성하였으며, 보기 번호와 내용을 가질 수 있도록 구성하였다.

class Quiz : 문제 클래스

public class Quiz{
    private int no;
    private String text;
    private List<Choice> choices;
}

문제 클래스는 Quiz라는 이름으로 작성하였으며, 퀴즈 번호와 내용, 그리고 보기 목록(List<Choice>)을 가질 수 있도록 구성하였다.

Quiz라는 형태를 myBatis에 ResultMap으로 등록하여 구문과 같이 연결해두면 계층형 데이터를 쉽게 불러올 수 있다.

ResultMap 설정

ResultMap은 mapper에 설정한다. quiz-mapper.xml이라는 파일을 만들고 내부에 다음과 같이 작성한다.

<!-- 기본 설정 생략 -->
<mapper namespace="quiz">

    <resultMap type="com.hakademy.vo.Quiz" id="quiz">
		<result column="no" property="no"/>
		<result column="text" property="text"/>
		<collection column="no" 
                                    property="choices" 
                                    javaType="java.util.List" 
                                    ofType="com.hakademy.vo.Choice" 
                                    select="getChoiceList"></collection>
	</resultMap>
	
    <select id="getQuizList" resultMap="quiz">
        select * from quiz order by no asc
    </select>
    
    <select id="getChoiceList" parameterType="int" resultType="com.hakademy.vo.Choice">
        select * from choice where quiz = #{quiz}
    </select>

</mapper>

해당 구문을 호출할 때에는 다음과 같이 작성한다.

List<Quiz> list = sqlSession.selectList("quiz.getQuizList");

ResultMap 설정 상세 설명

메인 select 구문

<select id="getQuizList" resultMap="quiz">
    select * from quiz order by no asc
</select>

메인 select 구문이며, 실행 결과 형태는 미리 정의된 quiz라는 id를 가진 resultMap이 할당된다. 이 구문을 실행한 결과를 myBatis가 자동으로 quiz에 맞게 처리하게 된다.

ResultMap 설정

<resultMap type="com.hakademy.vo.Quiz" id="quiz">
    <result column="no" property="no"/>
    <result column="text" property="text"/>
    <collection column="no" 
                                property="choices" 
                                javaType="java.util.List" 
                                ofType="com.hakademy.vo.Choice" 
                                select="getChoiceList"></collection>
</resultMap>

ResultMap을 만들기 위해서는 다음 설정을 수행해야 한다.

  • type : 매핑될 클래스명을 작성한다.

  • id : 외부에서 지정할 이름을 작성한다.

내부에는 <result> 항목을 배치하여 데이터에 대한 실제 매핑 관계를 설정한다.

Result 설정 항목은 다음과 같다.

  • column : 불러올 데이터베이스 항목명을 작성한다.

  • property : 불러온 항목을 저장할 클래스 내의 변수명을 작성한다.

만약 데이터가 여러 개라면 컬렉션을 지정할 수 있는데, 이 때는 <collection> 항목을 배치하여 매핑 관계를 설정한다.

Collection 설정 항목은 다음과 같다.

  • column : 불러올 데이터베이스 항목명을 작성한다. 여기서는 하위 SQL을 실행하기 위한 항목을 작성한다.(일반적으로 PK로 작성)

  • property : 불러온 항목을 저장할 클래스 내의 변수명을 작성한다.

  • javaType : Collection의 형태를 작성한다. 내장된 별칭이 있으므로 java.util.List 또는 list 라고 작성해도 무방하다.

  • ofType : Collection의 내용물의 형태를 작성한다. Quiz 클래스에 List<Choice>라고 되어 있으므로 Choice의 경로를 작성한다.

  • select : 이 데이터는 quiz에 존재하지 않기 때문에 조회하는 구문이 따로 필요하므로 해당하는 조회 구문의 id를 작성한다.

서브 select 구문

서브 select 구문은 ResultMap에 의해서 자동으로 호출된다.

<resultMap type="com.hakademy.vo.Quiz" id="quiz">
    <!-- 생략 -->
    <collection column="no" property="choices" select="getChoiceList"></collection>
</resultMap>

<select id="getChoiceList" parameterType="int" resultType="com.hakademy.vo.Choice">
    select * from choice where quiz = #{quiz}
</select>

필요한 부분을 제외한 나머지는 생략한 구문이다. ResultMap을 만들면서 <collection>이라는 구문을 만나면 myBatis에서는 다음의 작업을 수행한다.

  1. Collection의 column 에서 지정한 항목을 추출한다.

  2. Collection의 select 에서 지정한 구문을 호출하며 1번에서 추출한 값을 전달한다.

  3. 서브 select 구문이 실행되어 List<Choice> 형태의 데이터가 반환된다.

  4. Collection의 property 에 지정된 변수인 choices에 3번의 결과값을 설정한다.

결론

ResultMap을 이용하면 계층화된 데이터를 실제 구조와 동일하게 효과적으로 불러올 수 있다. 물론 for 구문을 이용하여 수동으로 수행해도 되지만 ResultMap을 이용하면 조금 더 구조적으로 접근할 수 있다. 단, select 구문이 여러 번(N+1) 실행되는 만큼 불러오는 데이터가 많은 경우는 권장하지 않는다.

Last updated