TokyoAJ

도쿄아재

SPRINGBOOT 2025.04.07

MyBatis에서 DTO 매핑 처리하는 방법 – Join & 다중 ResultMap 실무 예제

MyBatis는 SQL 중심의 프레임워크로, 복잡한 쿼리(조인, 서브쿼리 등)를 마음대로 다룰 수 있는 게 큰 장점입니다.

하지만 실무에서는 단순한 테이블 매핑을 넘어서 DTO 객체에 조인 결과를 매핑하는 일이 자주 발생하죠.

이 글에서는 MyBatis에서 JOIN 결과를 DTO에 매핑하는 3가지 주요 방법

OneToOne / OneToMany 관계를 처리하는 실전 패턴을 예제와 함께 소개합니다.


예제 도메인 구조

  1. user 테이블 (id, username)
  2. profile 테이블 (id, user_id, bio)

관계: User 1 : 1 Profile


1. 단순 Join → DTO 매핑 (ResultType)

DTO 클래스

public class UserProfileDto {
private Long userId;
private String username;
private String bio;
}


Mapper Interface

@Mapper
public interface UserMapper {
UserProfileDto selectUserWithProfileById(@Param("userId") Long userId);
}


XML Mapper

<select id="selectUserWithProfileById" resultType="com.example.dto.UserProfileDto">
SELECT
u.id AS userId,
u.username,
p.bio
FROM user u
JOIN profile p ON u.id = p.user_id
WHERE u.id = #{userId}
</select>

resultType을 DTO로 설정하고

SQL에서 컬럼 alias로 DTO 필드명과 정확히 매핑되게 작성합니다.


2. OneToOne 매핑 – resultMap + association

Entity 구조

public class User {
private Long id;
private String username;
private Profile profile; // 포함된 객체
}

public class Profile {
private Long id;
private String bio;
}


XML Mapper

<resultMap id="UserWithProfileMap" type="User">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<association property="profile" javaType="Profile">
<id property="id" column="profile_id"/>
<result property="bio" column="bio"/>
</association>
</resultMap>

<select id="selectUserWithProfile" resultMap="UserWithProfileMap">
SELECT
u.id AS user_id,
u.username,
p.id AS profile_id,
p.bio
FROM user u
LEFT JOIN profile p ON u.id = p.user_id
WHERE u.id = #{userId}
</select>

association은 단일 객체(OneToOne) 매핑에 사용됩니다.

SQL의 컬럼명을 DTO 필드명과 연결되도록 alias 처리합니다.


3. OneToMany 매핑 – collection 사용

관계: User 1:N Post

public class User {
private Long id;
private String username;
private List<Post> posts;
}

public class Post {
private Long id;
private String title;
}


XML Mapper

<resultMap id="UserPostMap" type="User">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="title" column="title"/>
</collection>
</resultMap>

<select id="selectUserWithPosts" resultMap="UserPostMap">
SELECT
u.id AS user_id,
u.username,
p.id AS post_id,
p.title
FROM user u
LEFT JOIN post p ON u.id = p.user_id
WHERE u.id = #{userId}
</select>

collection 태그는 List, Set 등 반복되는 자식 객체 매핑에 사용합니다.

기본적으로 MyBatis가 user_id를 기준으로 그룹핑합니다.


실무 팁

설명
alias 중요SQL의 컬럼 alias는 DTO 필드명 또는 resultMap 필드명과 정확히 일치해야 함
resultType vs resultMap단순 쿼리는 resultType으로 충분, 조인/관계형은 resultMap 권장
N+1 문제collection을 사용할 땐 주의. 대량 데이터는 쿼리 분리 or 배치 로딩 필요
Mapper 테스트복잡한 조인일수록 단위 테스트로 결과 DTO 구조 확인 필수


언제 어떤 방법을 쓸까?

상황추천 방식
단순 조인 + DTO 반환resultType + SQL alias
OneToOneassociation
OneToManycollection
복잡한 조건/서브쿼리resultMap or Custom DTO


마무리

MyBatis는 다양한 방식으로 조인 결과를 매핑할 수 있으며,

SQL과 Java 객체 구조 사이의 매핑을 세밀하게 제어할 수 있습니다.

SQL은 자유롭게, Java 객체는 깔끔하게.

이 조합을 통해 복잡한 도메인 구조도 안정적으로 표현할 수 있어요.



댓글