Mybatis에서 객체 간의 연관관계를 매핑할 때, 특히 다대일(Many-to-One)과 일대다(One-to-Many) 관계는 자주 마주치는 패턴이다. 여기서는 학생(Student)과 교사(Teacher)라는 간단한 도메인 모델을 통해 두 가지 관계를 각각의 접근 방식으로 구현해본다.
1. 다대일 관계 (여러 학생 → 한 명의 교사)
다수의 학생이 하나의 교사에 속하는 구조다. Student 엔티티에는 Teacher 객체가 필드로 포함된다.
엔티티 클래스
//Student.java
package com.example.domain;
import lombok.Data;
@Data
public class Student {
private int id;
private String name;
private Teacher teacher; // 다대일 관계 표현
}
//Teacher.java
package com.example.domain;
import lombok.Data;
@Data
public class Teacher {
private int id;
private String name;
}
방법 A: 서브쿼리 방식 (N+1 문제 가능)
메인 쿼리로 학생 목록을 가져온 후, 각 학생의 tid를 이용해 별도의 교사 조회 쿼리를 실행한다.
<!-- StudentMapper.xml -->
<select id="findAllWithTeacher" resultMap="studentTeacherResult">
SELECT * FROM student
</select>
<resultMap id="studentTeacherResult" type="com.example.domain.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher"
column="tid"
javaType="com.example.domain.Teacher"
select="findTeacherById"/>
</resultMap>
<select id="findTeacherById" resultType="com.example.domain.Teacher">
SELECT * FROM teacher WHERE id = #{tid}
</select>
방법 B: 조인 방식 (단일 쿼리)
SQL 조인을 통해 한 번에 모든 데이터를 가져온 뒤, ResultMap으로 객체를 조립한다.
<!-- StudentMapper.xml -->
<select id="findAllWithTeacherJoin" resultMap="studentTeacherJoinResult">
SELECT s.id AS sid, s.name AS sname,
t.id AS tid, t.name AS tname
FROM student s, teacher t
WHERE s.tid = t.id
</select>
<resultMap id="studentTeacherJoinResult" type="com.example.domain.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="com.example.domain.Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
2. 일대다 관계 (한 명의 교사 → 여러 학생)
반대로 한 교사가 여러 학생을 가르치는 구조다. Teacher 엔티티에는 List<Student> 필드가 필요하다.
엔티티 클래스
//Student2.java
package com.example.domain;
import lombok.Data;
@Data
public class Student2 {
private int id;
private String name;
private int tid;
}
//Teacher2.java
package com.example.domain;
import lombok.Data;
import java.util.List;
@Data
public class Teacher2 {
private int id;
private String name;
private List<Student2> students; // 일대다 관계 표현
}
방법 A: 서브쿼리 방식
<!-- TeacherMapper.xml -->
<select id="findTeacherWithStudents" resultMap="teacherStudentResult">
SELECT * FROM teacher WHERE id = #{id}
</select>
<resultMap id="teacherStudentResult" type="com.example.domain.Teacher2">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students"
javaType="java.util.ArrayList"
ofType="com.example.domain.Student2"
column="id"
select="findStudentsByTeacherId"/>
</resultMap>
<select id="findStudentsByTeacherId" resultType="com.example.domain.Student2">
SELECT * FROM student WHERE tid = #{id}
</select>
방법 B: 조인 방식
<!-- TeacherMapper.xml -->
<select id="findTeacherWithStudentsJoin" resultMap="teacherStudentJoinResult">
SELECT s.id AS sid, s.name AS sname,
t.id AS tid, t.name AS tname
FROM student s, teacher t
WHERE s.tid = t.id AND t.id = #{id}
</select>
<resultMap id="teacherStudentJoinResult" type="com.example.domain.Teacher2">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="com.example.domain.Student2">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
3. 핵심 정리
- <association> : 다대일 관계에서 '하나'의 대상 객체를 매핑할 때 사용한다.
- <collection> : 일대다 관계에서 '여러' 객체를 컬렉션으로 매핑할 때 사용한다.
- javaType : 자바 객체의 실제 타입을 명시한다. collection에서는 주로 ArrayList를 지정한다.
- ofType : 컬렉션 내부에 담길 제네릭 타입을 지정한다. 예: Student2.class
- 서브쿼리 방식 : 매핑 로직이 직관적이나, N+1 쿼리 문제가 발생할 수 있어 주의가 필요하다.
- 조인 방식 : 한 번의 SQL로 모든 데이터를 가져오므로 성능상 유리하지만, ResultMap이 다소 복잡해질 수 있다.