So I'm experimenting with Spring Data more specifically relationships (many to many).
This is what I have at the moment:
COURSE:
@Entity
@Table(name = "COURSE")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer courseId;
@Column
private String name;
@ManyToMany(mappedBy = "courses", fetch = FetchType.EAGER)
List<Student> students;
public Course(String name) {
this.name = name;
//this.department = department;
}
protected Course() {
}
public Integer getId() {
return courseId;
}
public String getName() {
return name;
}
public List<Student> getStudents(){ return students;}
@Override
public String toString() {
return "Course{" +
"id=" + courseId + ", name='" + name + '\'';
}
}
STUDENT:
@Entity
@Table(name = "STUDENT")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer studentId;
@Column
private boolean fullTime;
@Column
private Integer age;
@Embedded
private Person attendee;
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinTable(name = "students_courses",
joinColumns = @JoinColumn(name = "student_Id", referencedColumnName = "studentId"),
inverseJoinColumns = @JoinColumn(name = "course_Id", referencedColumnName = "courseId"))
private List<Course> courses;// = new ArrayList<>();
public Student(Person attendee, boolean fullTime, Integer age) {
this.attendee = attendee;
this.fullTime = fullTime;
this.age = age;
courses = new ArrayList<>();
}
protected Student() {
}
public Integer getStudentId() {
return studentId;
}
public Person getAttendee() {
return attendee;
}
public void setAge(Integer age) {
this.age = age;
}
public boolean isFullTime() {
return fullTime;
}
public Integer getAge() {
return age;
}
public List<Course> getCourses() {
return courses;
}
@Override
public String toString() {
StringBuilder courseStr = new StringBuilder();
getCourses().forEach(course -> courseStr.append(course.getName()));
return "Student{" + "studentId=" + studentId + ", " + attendee + ", fullTime=" + fullTime +
", age=" + age + " , course=" + courseStr.toString() +"}\n";
}
}
My test case:
@Test
public void simpleStudentCrudExample() {
boolean fullTime = true;
studentRepository.save(new Student(new Person("jane", "doe"), fullTime, 20));
studentRepository.save(new Student(new Person("john", "doe"), fullTime, 22));
studentRepository.save(new Student(new Person("mike", "smith"), fullTime, 18));
studentRepository.save(new Student(new Person("ally", "kim"), !fullTime, 19));
studentRepository.save(new Student(new Person("ally", "kim"), !fullTime, 19));
Student s1 = new Student(new Person("Bob", "Cho"), fullTime, 26);
s1.getCourses().add(new Course("Multithreading"));
studentRepository.save(s1);
createCourse();
System.out.println("\n*************Printing Original Students*************");
studentRepository.findAll().forEach(System.out::println);
System.out.println("\n*************Printing Courses*************");
courseRepository.findAll().forEach(System.out::println);
List<Course> courseList = courseRepository.findAll();
//age up the students and add course
studentRepository.findAll().forEach(student -> {
List<Course> collect = courseRepository.findAll().stream()
.filter(course2 -> course2.getId() != 1)
.collect(Collectors.toList());
student.setAge(student.getAge() + 1);
student.getCourses().addAll(collect);
Student save = studentRepository.save(student);
});
System.out.println("\n*************Students a year older and course added*************");
for (Student student : studentRepository.findAll()) {
System.out.println(student);
}
studentRepository.deleteById(6);
System.out.println("\n*************Deleted student by id 6*************");
for (Student student : studentRepository.findAll()) {
System.out.println(student);
}
System.out.println("\n*************Printing Courses*************");
courseRepository.findAll().forEach(System.out::println);
}
private void createCourse() {
course = new Course("Algorithm");//, department);
course1 = new Course("Databases");//, department);
courseRepository.save(course);
courseRepository.save(course1);
}
Output:
*************Printing Original Students*************
2021-12-12 13:09:37.297 INFO 12484 --- [ main] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
Student{studentId=1, firstName='jane', lastname='doe' , fullTime=true, age=20 , course=}
Student{studentId=2, firstName='john', lastname='doe' , fullTime=true, age=22 , course=}
Student{studentId=3, firstName='mike', lastname='smith' , fullTime=true, age=18 , course=}
Student{studentId=4, firstName='ally', lastname='kim' , fullTime=false, age=19 , course=}
Student{studentId=5, firstName='ally', lastname='kim' , fullTime=false, age=19 , course=}
Student{studentId=6, firstName='Bob', lastname='Cho' , fullTime=true, age=26 , course=Multithreading}
*************Printing Courses*************
Course{id=1, name='Multithreading'
Course{id=2, name='Algorithm'
Course{id=3, name='Databases'
*************Students a year older and course added*************
Student{studentId=1, firstName='jane', lastname='doe' , fullTime=true, age=21 , course=AlgorithmDatabases}
Student{studentId=2, firstName='john', lastname='doe' , fullTime=true, age=23 , course=AlgorithmDatabases}
Student{studentId=3, firstName='mike', lastname='smith' , fullTime=true, age=19 , course=AlgorithmDatabases}
Student{studentId=4, firstName='ally', lastname='kim' , fullTime=false, age=20 , course=AlgorithmDatabases}
Student{studentId=5, firstName='ally', lastname='kim' , fullTime=false, age=20 , course=AlgorithmDatabases}
Student{studentId=6, firstName='Bob', lastname='Cho' , fullTime=true, age=27 , course=MultithreadingAlgorithmDatabases}
*************Deleted student by id 6*************
Student{studentId=1, firstName='jane', lastname='doe' , fullTime=true, age=21 , course=AlgorithmDatabases}
Student{studentId=2, firstName='john', lastname='doe' , fullTime=true, age=23 , course=AlgorithmDatabases}
Student{studentId=3, firstName='mike', lastname='smith' , fullTime=true, age=19 , course=AlgorithmDatabases}
Student{studentId=4, firstName='ally', lastname='kim' , fullTime=false, age=20 , course=AlgorithmDatabases}
Student{studentId=5, firstName='ally', lastname='kim' , fullTime=false, age=20 , course=AlgorithmDatabases}
*************Printing Courses*************
Course{id=2, name='Algorithm'
Course{id=3, name='Databases'
As you can see from the output, everything works. But I was wondering why the multithreading course got deleted despite only deleting the student that was associated to it. From a database perspective, I was under the impression that it would only delete the targeted entity/record and not anything it's related to (in this case; the multithreading course). Am I right with this assumption? Is it normal from a database perspective to delete records that are related to the record you are deleting?
Furthermore, I am seeing this effect because in the Student class, I am using cascade = CascadeType.ALL. If I delete this then I get the following error:
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.university.domain.Course; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.university.domain.Course
This occurs when the code is trying to save the s1 student object. Am I right in assuming that I should: save the multithreading course, then fetch the multithreading course and then add it to s1's course list? But my confusion is when should I use cascading ALL and when should I not use it.