MyBatis는 SQL 중심의 프레임워크로, 복잡한 쿼리(조인, 서브쿼리 등)를 마음대로 다룰 수 있는 게 큰 장점입니다.
하지만 실무에서는 단순한 테이블 매핑을 넘어서 DTO 객체에 조인 결과를 매핑하는 일이 자주 발생하죠.
이 글에서는 MyBatis에서 JOIN 결과를 DTO에 매핑하는 3가지 주요 방법과
OneToOne / OneToMany 관계를 처리하는 실전 패턴을 예제와 함께 소개합니다.
예제 도메인 구조
- user 테이블 (id, username)
- 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 |
OneToOne | association |
OneToMany | collection |
복잡한 조건/서브쿼리 | resultMap or Custom DTO |
마무리
MyBatis는 다양한 방식으로 조인 결과를 매핑할 수 있으며,
SQL과 Java 객체 구조 사이의 매핑을 세밀하게 제어할 수 있습니다.
SQL은 자유롭게, Java 객체는 깔끔하게.
이 조합을 통해 복잡한 도메인 구조도 안정적으로 표현할 수 있어요.
댓글