이번 MyBatis3 프로젝트에서는 이전 MyBatis2 프로젝트에서 학습했던 1:N(회원-주문) 관계를 확장하여 학생(Student)과 강의(Course)의 다대다(M:N) 관계를 구현하였다.
기존 프로젝트에서는 하나의 회원이 여러 주문을 가지는 구조였기 때문에 하나의 객체가 다른 객체의 리스트를 가지는 형태만 구현하면 되었다. 하지만 이번 프로젝트에서는 학생은 여러 강의를 수강할 수 있고, 하나의 강의 역시 여러 학생이 수강할 수 있기 때문에 중간 테이블(Enrollment)을 이용한 관계 표현을 새롭게 학습하였다.
이를 통해 관계형 데이터베이스의 M:N 관계를 Java 객체에서는 어떻게 표현하는지 이해할 수 있었다.
학생과 강의를 등록하는 구조는 기존 프로젝트와 크게 다르지 않다.
@PostMapping("/students")
public String addStudent(@ModelAttribute Student student) {
enrollService.createStudent(student);
return "redirect:/";
}
@PostMapping("/courses")
public String addCourse(@ModelAttribute Course course) {
enrollService.createCourse(course);
return "redirect:/";
}
학생과 강의를 등록하는 방식은 이전 프로젝트의 Member, Order 등록과 동일하게 Controller → Service → Mapper 순으로 동작한다.
Controller는 사용자의 요청을 받아 Service에 전달하고, Service는 Mapper를 호출하여 데이터베이스에 저장한다. 이를 통해 MVC 구조는 그대로 유지하면서도 새로운 도메인(Student, Course)으로 확장할 수 있다는 점을 확인하였다.
이번 프로젝트에서 가장 새롭게 배운 기능은 학생과 강의를 연결하는 수강 신청 기능이다.
@PostMapping("/enrollments")
public String addEnrollment(
@RequestParam Long studentId,
@RequestParam Long courseId) {
Student student = new Student();
student.setId(studentId);
Course course = new Course();
course.setId(courseId);
enrollService.createEnrollment(student, course);
return "redirect:/";
}
사용자로부터 전달되는 값은
두 개의 기본키이다.
하지만 Mapper에서는 단순한 숫자가 아니라 Student 객체와 Course 객체를 전달받는다.
따라서 Controller에서는
Student student = new Student();
student.setId(studentId);
Course course = new Course();
course.setId(courseId);
와 같이 객체를 생성한 뒤 id만 설정하여 Service에 전달한다.
이 과정은 이전 프로젝트에서 Order 객체 안에 Member 객체를 저장했던 방식과 유사하지만, 이번에는 두 개의 객체를 동시에 전달하여 중간 테이블에 저장한다는 점이 가장 큰 차이점이다.
기존 프로젝트의 Mapper는 CRUD와 1:N 관계 조회 정도의 기능만 담당하였다.
이번 프로젝트에서는 Mapper가 학생, 강의, 수강신청, 다대다 조회까지 담당하도록 확장되었다.
@Mapper
public interface EnrollMapper {
void insertStudent(Student student);
void insertCourse(Course course);
void insertEnrollment(Student student, Course course);
List<StudentWithCoursesDTO> findAllStudentsWithCourses();
List<CourseWithStudentsDTO> findAllCoursesWithStudents();
}
기존에는 하나의 객체를 기준으로 조회하였다면, 이번 프로젝트에서는
두 방향 모두 조회할 수 있도록 Mapper가 구성되었다.
이를 통해 하나의 관계를 다양한 관점에서 조회하는 방법을 학습하였다.
이번 프로젝트에서는 학생이 수강 중인 강의를 저장하기 위한 DTO가 추가되었다.
public class StudentWithCoursesDTO {
private Long id;
private String name;
private List<Course> courses = new ArrayList<>();
}
이 DTO는 학생 한 명이 여러 강의를 수강하는 관계를 표현한다.
예를 들어
김학생
├── Java
├── Database
└── Spring
처럼 하나의 Student 객체 안에 여러 Course 객체를 저장할 수 있다.
이전 프로젝트에서는 Member가 Order 목록을 가지는 구조였다면, 이번에는 Student가 Course 목록을 가진다는 점이 달라졌다.
반대로 강의를 기준으로도 학생 목록을 조회할 수 있도록 DTO를 작성하였다.
public class CourseWithStudentsDTO {
private Long id;
private String name;
private List<Student> students = new ArrayList<>();
}
이 DTO는 하나의 강의를 여러 학생이 수강하는 관계를 표현한다.
예를 들어
Spring
├── 김학생
├── 이학생
└── 박학생
처럼 하나의 Course 객체가 여러 Student 객체를 포함하게 된다.
기존 프로젝트에서는 한 방향의 관계만 표현했지만, 이번 프로젝트에서는 학생 기준과 강의 기준 양방향 조회를 구현하면서 관계 매핑에 대한 이해를 넓힐 수 있었다.
Service 역시 기존보다 기능이 확장되었다.
public List<StudentWithCoursesDTO> findAllStudentsWithCourses() {
return enrollMapper.findAllStudentsWithCourses();
}
public List<CourseWithStudentsDTO> findAllCoursesWithStudents() {
return enrollMapper.findAllCoursesWithStudents();
}
기존에는 Member와 Order의 관계만 조회하였다면, 이번에는
두 가지 조회 기능을 모두 Service에서 제공한다.
이를 통해 하나의 관계를 다양한 관점에서 활용할 수 있는 구조를 구현하였다.
MainController에서는 학생, 강의, 학생별 수강 목록, 강의별 수강 학생 목록을 모두 Model에 담아 View로 전달한다.
model.addAttribute("students", enrollService.findAllStudents());
model.addAttribute("courses", enrollService.findAllCourses());
model.addAttribute("studentWithCourses", enrollService.findAllStudentsWithCourses());
model.addAttribute("courseWithStudents", enrollService.findAllCoursesWithStudents());
이전 프로젝트에서는 하나의 관계 조회만 수행하였다면, 이번 프로젝트에서는 같은 데이터를 학생 중심과 강의 중심으로 각각 조회하여 화면에 출력하였다. 이를 통해 MyBatis의 resultMap을 활용한 다양한 객체 매핑 방식을 경험할 수 있었다.
| 이전 MyBatis2 프로젝트 | MyBatis3 프로젝트 |
|---|---|
| 1:N(Member-Order) 관계 | M:N(Student-Course) 관계 |
| 하나의 관계 조회 | 양방향 관계 조회 |
| MemberWithOrdersDTO | StudentWithCoursesDTO, CourseWithStudentsDTO |
| FK를 객체로 표현 | 중간 테이블을 이용한 객체 연결 |
| JOIN 결과 한 방향 매핑 | JOIN 결과를 두 방향으로 매핑 |
이번 프로젝트에서는 단순히 데이터를 조회하는 수준을 넘어 다대다(M:N) 관계를 객체지향적으로 표현하는 방법을 학습하였다. 특히 학생과 강의 사이에 직접 관계를 맺는 것이 아니라 중간 테이블을 통해 연결한다는 점과, 이를 Java에서는 객체와 DTO를 이용해 자연스럽게 표현할 수 있다는 점이 인상적이었다.
또한 StudentWithCoursesDTO와 CourseWithStudentsDTO를 각각 구현하면서 하나의 관계를 학생 중심과 강의 중심 두 가지 관점으로 조회할 수 있는 구조를 경험하였다. 이전 프로젝트에서 학습한 1:N 관계를 기반으로 M:N 관계까지 확장해 보면서 MyBatis의 resultMap과 객체 매핑 원리를 한 단계 더 깊게 이해할 수 있었다.