ResultMap이란
myBatis에서 제공하는 자동 매핑으로 해결이 어려운 경우를 위해 구조를 설계할 수 있도록 만들어진 도구이다.
ResultMap이 필요한 경우
ResultMap은 다음과 같은 데이터 구조를 불러올 때 적합하다.
계층형 데이터 구조
객관식 시험과 관련된 정보들을 데이터베이스에 추가한다면 다음과 같은 구조를 가지게 된다.
- 1번 시험문제
- 1번 보기
- 2번 보기
- 3번 보기
- 4번 보기
- 2번 시험문제
- 1번 보기
- 2번 보기
- 3번 보기
- 4번 보기
시험이라는 상위 entity와 보기라는 하위 entity로 나누어 생각해본다면 다음과 같이 이해할 수 있다.
물리 테이블은 다음과 같이 구성될 것이다.
문제 테이블(quiz)
문제번호(no)문제내용(text)
보기 테이블(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을 이용하면 좋다.
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에서는 다음의 작업을 수행한다.
- Collection의 column 에서 지정한 항목을 추출한다.
- Collection의 select 에서 지정한 구문을 호출하며 1번에서 추출한 값을 전달한다.
- 서브 select 구문이 실행되어 List<Choice> 형태의 데이터가 반환된다.
- Collection의 property 에 지정된 변수인 choices에 3번의 결과값을 설정한다.
결론
ResultMap을 이용하면 계층화된 데이터를 실제 구조와 동일하게 효과적으로 불러올 수 있다.
물론 for 구문을 이용하여 수동으로 수행해도 되지만 ResultMap을 이용하면 조금 더 구조적으로 접근할 수 있다.
단, select 구문이 여러 번(N+1) 실행되는 만큼 불러오는 데이터가 많은 경우는 권장하지 않는다.
참고 : http://www.sysout.co.kr/home/webbook/page/read/643;jsessionid=54B3D5320E73A41FC4088D5E3D322141