// Developer · Debugging Reports

뉴스도 디버깅이 필요해

마주친 오류를 재현하고, 원인을 추적하고, 기록합니다. Java · Spring · DB · 실무 트러블슈팅.

debug — today.log
$ debug --trace "오늘의 뉴스"
> 헤드라인 파싱 중...
> 원인 추적: 근거 누락 · 출처 불명
> 패치 적용: 사실 확인 + 출처 보강
✓ RESOLVED
TOTAL
TODAY
YESTERDAY

Categories

웹개발/Mybatis

MyBatis resultMap과 resultType 차이 실무 예제로 정리

자바를잡아2026. 6. 25. 08:00
반응형

resultType과 resultMap, 무엇이 다른가

MyBatis로 조회 쿼리를 작성하다 보면 결과를 어디에 담을지 지정해야 한다. 이때 쓰는 게 resultTyperesultMap이다. 둘 다 SELECT 결과를 자바 객체로 매핑한다는 점은 같지만, 매핑을 자동으로 맡기느냐 직접 정의하느냐에서 갈린다. 이 차이를 모르면 "분명 쿼리는 맞는데 일부 필드가 null로 들어오는" 상황에서 한참 헤매게 된다. MyBatis resultMap resultType 차이를 실무 기준으로 정리한다.

간단히 말하면 resultType은 "결과를 이 타입에 알아서 담아라", resultMap은 "이 컬럼은 이 필드에 이렇게 담아라"를 직접 적어 주는 방식이다.

차이 비교표

구분 resultType resultMap
매핑 방식 컬럼명-필드명 자동 매핑 매핑 규칙을 직접 정의
설정량 적음 (타입만 지정) 많음 (id/result 정의 필요)
컬럼명 불일치 별칭(alias)이나 설정 필요 property/column으로 직접 지정
1:1 매핑 제한적 association으로 처리
1:N 매핑 어려움 collection으로 처리
재사용 불가 여러 쿼리에서 재사용 가능
적합한 상황 단순 단일 테이블 조회 복잡한 조인, 이름 불일치, 중첩 객체

resultType 기본 사용

1. 단순 조회는 resultType으로 충분

컬럼명과 자바 객체의 필드명이 일치하면 resultType만으로 깔끔하게 매핑된다. 단일 테이블 조회 대부분이 여기에 해당한다.

<!-- MemberMapper.xml -->
<select id="selectMember" parameterType="string" resultType="com.example.vo.MemberVO">
    SELECT member_id, member_nm, email
    FROM   member
    WHERE  member_id = #{memberId}
</select>

여기서 member_id 컬럼은 VO의 member_id 필드(또는 카멜케이스 설정 시 memberId)에 담긴다. resultType 값으로는 패키지까지 포함한 클래스명을 쓰거나, typeAlias를 등록했다면 별칭을 쓴다.

2. 컬럼명과 필드명이 다를 때 생기는 문제

실무에서 가장 많이 막히는 지점이다. DB는 보통 member_id처럼 스네이크케이스를 쓰고, 자바는 memberId 카멜케이스를 쓴다. 이 상태로 resultType만 쓰면 해당 필드가 null로 들어온다. 매핑할 짝을 못 찾기 때문이다. 해결책은 두 가지다. 쿼리에서 별칭을 주거나, MyBatis 전역 설정을 켜는 것이다.

<!-- 방법 A: 쿼리에서 별칭(alias) 부여 -->
SELECT member_id AS memberId, member_nm AS memberNm
FROM   member;

<!-- 방법 B: mybatis-config.xml 전역 설정 -->
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

방법 B를 켜면 member_id 컬럼이 자동으로 memberId 필드에 매핑된다. 프로젝트 전체에 일관되게 적용되므로 신규 프로젝트라면 보통 이 설정을 켜고 시작한다. 다만 기존 레거시에서 중간에 켜면 기존 별칭과 충돌하거나 동작이 달라질 수 있으니, 운영 반영 전 테스트 환경에서 먼저 확인한다.

resultMap이 필요한 경우

1. 컬럼-필드 매핑을 직접 정의

전역 설정을 켜기 어렵거나, 특정 쿼리만 매핑을 명시하고 싶을 때 resultMap을 쓴다. id는 식별자 컬럼, result는 일반 컬럼 매핑이다.

<!-- MemberMapper.xml -->
<resultMap id="memberResultMap" type="com.example.vo.MemberVO">
    <id     property="memberId" column="member_id"/>
    <result property="memberNm" column="member_nm"/>
    <result property="email"    column="email"/>
</resultMap>

<select id="selectMember" parameterType="string" resultMap="memberResultMap">
    SELECT member_id, member_nm, email
    FROM   member
    WHERE  member_id = #{memberId}
</select>

한 번 정의한 resultMap은 여러 select에서 resultMap="memberResultMap"으로 재사용할 수 있다. 같은 VO를 여러 쿼리가 공유한다면 중복을 줄일 수 있다.

2. 1:1 관계는 association

회원 한 명에 부서 정보 하나가 붙는 식의 1:1 조인은 association으로 중첩 객체를 매핑한다.

<resultMap id="memberWithDept" type="com.example.vo.MemberVO">
    <id     property="memberId" column="member_id"/>
    <result property="memberNm" column="member_nm"/>
    <association property="dept" javaType="com.example.vo.DeptVO">
        <id     property="deptId"  column="dept_id"/>
        <result property="deptNm"  column="dept_nm"/>
    </association>
</resultMap>

이렇게 하면 MemberVO 안에 DeptVO 객체가 채워진다. resultType으로는 이런 중첩 구조를 깔끔하게 담기 어렵다.

3. 1:N 관계는 collection

부서 하나에 회원 여러 명이 붙는 1:N은 collection으로 리스트를 매핑한다. resultMap이 사실상 필수인 대표 상황이다.

<resultMap id="deptWithMembers" type="com.example.vo.DeptVO">
    <id     property="deptId" column="dept_id"/>
    <result property="deptNm" column="dept_nm"/>
    <collection property="memberList" ofType="com.example.vo.MemberVO">
        <id     property="memberId" column="member_id"/>
        <result property="memberNm" column="member_nm"/>
    </collection>
</resultMap>

조인 결과에서 부서별로 회원 행이 여러 개 나오는데, collection이 이를 하나의 DeptVO 안 리스트로 묶어 준다. id 태그가 식별자 역할을 해서 같은 부서끼리 그룹핑되므로, id를 빼먹으면 중복 행이 제대로 묶이지 않을 수 있다.

선택 기준 정리

  • 단일 테이블 단순 조회, 컬럼명-필드명 일치(또는 카멜케이스 설정 ON) → resultType
  • 컬럼명과 필드명이 많이 다르고 특정 쿼리만 명시하고 싶다 → resultMap
  • 1:1 중첩 객체가 필요하다 → resultMap + association
  • 1:N 리스트 매핑이 필요하다 → resultMap + collection
  • 여러 쿼리가 같은 매핑을 공유한다 → resultMap 재사용

실무에서 자주 하는 실수

  • resultType과 resultMap을 한 select에 동시에 지정하는 경우. 둘 중 하나만 쓴다.
  • 카멜케이스 설정을 켜 놓고도 일부 쿼리에 별칭을 또 줘서 오히려 혼선이 생기는 경우. 프로젝트 컨벤션을 하나로 통일한다.
  • collection 매핑에서 id 태그를 생략해 같은 부모 행이 제대로 그룹핑되지 않는 경우.
  • resultType에 잘못된 패키지 경로나 오타가 있어 클래스를 못 찾는 경우. typeAlias 등록 여부도 함께 확인한다.

FAQ

Q1. resultType으로 했더니 일부 필드가 null입니다.

컬럼명과 필드명이 달라서 매핑이 안 된 경우다. 쿼리에 별칭을 주거나 mapUnderscoreToCamelCase 설정을 켠다. 그래도 안 되면 resultMap으로 직접 매핑한다.

Q2. resultMap과 resultType을 같이 쓸 수 있나요?

하나의 select에는 둘 중 하나만 지정한다. 동시에 쓰면 안 된다. 복잡한 매핑이 필요하면 resultMap을 쓴다.

Q3. 1:N 조회는 꼭 resultMap을 써야 하나요?

중첩 리스트를 객체로 담으려면 collection이 필요하고, collection은 resultMap 안에서 정의한다. 그래서 1:N 매핑은 resultMap이 사실상 필수다.

Q4. resultType이 resultMap보다 느린가요?

단순 매핑에서는 체감 차이가 거의 없다. 성능보다는 매핑의 복잡도와 가독성, 재사용 여부로 선택하는 것이 맞다.

Q5. resultMap의 id와 result 태그는 뭐가 다른가요?

id는 식별자(주로 PK) 컬럼을 가리키고, result는 일반 컬럼이다. collection/association에서 행을 그룹핑할 때 id 기준으로 묶이므로, 식별자에는 id 태그를 쓰는 것이 좋다.

정리

MyBatis resultMap resultType 차이는 결국 매핑을 자동에 맡기느냐, 직접 정의하느냐로 요약된다. 컬럼명과 필드명이 맞아떨어지는 단순 조회는 resultType으로 충분하고, 이름이 다르거나 1:1·1:N 같은 중첩 구조가 필요하면 resultMap을 쓴다. 카멜케이스 자동 변환 설정을 켜면 resultType만으로 처리되는 범위가 넓어지지만, 기존 프로젝트에 중간 적용할 때는 동작 변화에 주의한다. 단순함은 resultType, 통제력과 재사용은 resultMap이라고 기억하면 선택이 쉬워진다.

반응형