Challenge
There is a Many to Many relationship between the course
and student
tables.
Launch the Starter Project
One to Many
One student can have Many grades. Many grades belong to One student.
The student_id
column joins both tables.
One to Many
One course can have Many grades. Many grades belong to One course.
The course_id
column joins both tables.
Many to Many
One student can enroll in Many courses. One course can contain Many students.
The course_student
table joins both tables.
Harry took four courses.
Hermione took four courses.
Herbology contains three students.
Alchemy contains four students.
The table that joins course
and student
is called a Join Table.
Task 1
Inside Course
, declare a @ManyToMany
relationship with a List
of students.
@ManyToMany private List<Student> students;
Task 2
Inside Student
, declare a @ManyToMany
relationship with a List
of courses.
Who will own the association?
In the One to Many relationship between student
and grade
, the foreign key lives inside grade
.
As a result, grade
owns the association.
@Table(name = "grade")
public class Grade {
@ManyToOne
@JoinColumn(name = "student_id", referencedColumnName = "id")
private Student student;
}
In the One to Many relationship between course
and grade
, the foreign key column lives inside grade
.
As a result, grade
owns the association.
@Table(name = "grade")
public class Grade {
@ManyToOne
@JoinColumn(name = "student_id", referencedColumnName = "id")
private Student student;
In a Many to Many relationship, the joining columns live inside of the join table.
So it doesn't matter who owns the association.
Task 3
For the sake of this exercise, Course
will own the association. Followed by its @ManyToMany
annotation, define a @JoinTable
.
- Set the name equal to
course_student
The parameters that follow define the join table's foreign key columns. Each foreign key column references the primary key of one table.
- Set
joinColumns
equal to a foreign key column@JoinColumn
that references the primary table of the entity owning the association. - Set
inverseJoinColumns
equal to a foreign key column@JoinColumn
that references the primary table of the entity that does not own the association.
Expected output
The course_student
join table appears, but Spring Boot creates another one by default.
Task 4
Place the mappedBy
parameter on the non-owning side of the relationship.
Expected output
Task 5
In order to avoid a recursive loop, add @JsonIgnore
to at least one side of the relationship. For this exercise, add it to the non-owning side.
@JsonIgnore @ManyToMany(mappedBy = "students") private List<Course> courses;
Task 6
Inside the CourseServiceImpl
, finish writing the code for addStudentToCourse
:
//TODO... course.getStudents().add(unwrappedStudent); return courseRepository.save(course);
Task 7
Inside the CourseController
, finish writing the code for enrollStudentToCourse
.
Task 8
From Postman, create a new PUT request under the Course
folder.
Make four PUT requests to localhost:8080/course/1/student/(1 to 4)
.
Postman Output
{ "id": 1, "subject": "Charms", "code": "CH104", "description": "In this class, you will learn spells concerned with giving an object new and unexpected properties.", "students": [ { "id": 2, "name": "Ron Weasley", "birthDate": "1980-03-01" }, { "id": 3, "name": "Hermione Granger", "birthDate": "1979-09-19" }, { "id": 4, "name": "Neville Longbottom", "birthDate": "1980-07-30" }, { "id": 1, "name": "Harry Potter", "birthDate": "1980-07-31" } ] }
H2 Output
Task 9
Currently, you can assign a course duplicate students. Try adding Ron to Charms twice.
In both POJO classes, replace the List
collection type with a Set
. A Set
is a collection type that prevents the addition of duplicate elements. A student cannot enroll in duplicate courses.
private Set<Course> courses;
Similarly, a course cannot contain duplicate students.
private Set<Student> students;
Once again, test your application by trying to duplicate Ron's enrollment in charms.
Task 10
- Inside
CourseServiceImpl
, finish writing thegetEnrolledStudents()
method. - Inside
CourseController
, finish writing thegetEnrolledStudents()
handler method. - Inside
StudentServiceImpl
, finish writing thegetEnrolledCourses()
method. - Inside
StudentController
, finish writing thegetEnrolledCourses()
handler method.
Task 11
- From Postman, create a new GET request under the
Student
folder.
- From Postman, create a new GET request under the
Course
folder.
Task 12
Whenever we query a course, we want to exclude the collection of students from the JSON. Otherwise, the response will be messy and convoluted. Put the @JSONIgnore
annotation on the owner side too.
@JsonIgnore @ManyToMany @JoinTable( name = "course_student", joinColumns = @JoinColumn(name = "course_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "student_id", referencedColumnName = "id") ) private Set<Student> students;
Task 13
Make 12 PUT
requests:
- Enroll Harry in Charms and Potions.
- Enroll Ron in History of Magic and Transfiguration
- Enroll Hermione in Charms, History of Magic, Potions, and Transfiguration
- Enroll Neville in Charms, Potions, and Herbology.
localhost:8080/course/1/student/1
localhost:8080/course/5/student/1
localhost:8080/course/4/student/2
localhost:8080/course/6/student/2
localhost:8080/course/1/student/3
localhost:8080/course/4/student/3
localhost:8080/course/5/student/3
localhost:8080/course/6/student/3
localhost:8080/course/1/student/4
localhost:8080/course/5/student/4
localhost:8080/course/3/student/4
Output
From Postman, read all of the courses that Harry is enrolled in:
[ { "id": 1, "subject": "Charms", "code": "CH104", "description": "In this class, you will learn spells concerned with giving an object new and unexpected properties." }, { "id": 5, "subject": "Potions", "code": "POT102", "description": "In this class, you will learn correct mixing and stirring of ingredients to create mixtures with magical effects." } ]
From Postman, read all of the courses that Hermione is enrolled in:
[ { "id": 1, "subject": "Charms", "code": "CH104", "description": "In this class, you will learn spells concerned with giving an object new and unexpected properties." }, { "id": 4, "subject": "History of Magic", "code": "HIS393", "description": "In this class, you will learn about significant events in wizarding history." }, { "id": 6, "subject": "Transfiguration", "code": "TR442", "description": "In this class, you will learn the art of changing the form or appearance of an object." }, { "id": 5, "subject": "Potions", "code": "POT102", "description": "In this class, you will learn correct mixing and stirring of ingredients to create mixtures with magical effects." } ]
From Postman, read all of the students enrolled in Potions.
[ { "id": 1, "name": "Harry Potter", "birthDate": "1980-07-31" }, { "id": 3, "name": "Hermione Granger", "birthDate": "1979-09-19" }, { "id": 4, "name": "Neville Longbottom", "birthDate": "1980-07-30" } ]
Task 14
Inside GradeServiceImpl.saveGrade()
, throw a new StudentNotEnrolledException()
if the client assigns a student a grade on a course they are not enrolled in.
@Override
public Grade saveGrade(Grade grade, Long studentId, Long courseId) {
Student student = studentRepository.findById(studentId).get();
Course course = courseRepository.findById(courseId).get();
//TODO: if student not enrolled in course, throw StudentNotEnrolledException
grade.setStudent(student);
grade.setCourse(course);
return gradeRepository.save(grade);
}
Handle the exception by adding StudentNotEnrolledException.class
as an argument for the @ExceptionHandler
: handleResourceNotFoundException
.
Task 15
Enroll Harry in Charms. Then, give him an A+.
{ "id": 1, "score": "A+", "student": { "id": 1, "name": "Harry Potter", "birthDate": "1980-07-31" }, "course": { "id": 1, "subject": "Charms", "code": "CH104", "description": "In this class, you will learn spells concerned with giving an object new and unexpected properties." } }
Now, give him a grade on a course that he's not enrolled in.
{ "timestamp": "07-08-2022 01:03:57", "message": [ "The student with id: '1' is not enrolled in the course with id: '2" ] }