Pujan Bhandari
Pujan Bhandari

Reputation: 33

How do i persist the parent entity without updating the child entity where they have many to many relationship?

I have a parent entity A which has many to many realtion with entity B and entity C which are mapped in A_B table and A_C table . While persisting I want to persist only A,A_B and A_c without persisting B and C . If I dont use cascadetype.all i get "object references an unsaved transient instance - save the transient instance before flushing" error . If i use cascade.all all the tables gets updated.

This is my parent Entity Student(getters and setters are present but not shown)

@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int studentId;
......
...... 
@ManyToMany()
@JoinTable(name = "student_preferredCountry",joinColumns = @JoinColumn(name 
= "studentId"),inverseJoinColumns = @JoinColumn(name = "countryId"))
private Set<Country> countries;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "student_preferred_course",joinColumns = @JoinColumn(name 
= "studentId"),inverseJoinColumns = @JoinColumn(name = "courseId"))
private Set<Course> courses;

This is my child entity Course

@Entity
public class Course {
private String courseName;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int courseId;
@ManyToMany(mappedBy = "courses")
private List<Student> coursestudents;

This is my another child entity Country

@Entity
public class Country {
@Id
private int countryId;
private String countryName;
@ManyToMany(mappedBy = "countries")
private List <Student> students;

This is how i persist

public boolean studententry(@RequestBody Student stud){
studentRepository.save(stud);

This is the sample json request

{

"studentFname": "fdfd",
"studentMname": "dfdf",
"studentLname": "fdf",
"studentFatherName": "fd",
"studentMotherName": "dfd",
"studentEmail": "dfd",
"studentAddress": "df",
"studentPhone": "df",
"countries": [
    {
        "countryId": "1",
        "countryName": "aus"
    },
    {
        "countryId": "2",
        "countryName": "newz"
    }
],
"courses": [
    {
        "course_id": "1",
        "course_name": "IELTS"
    },
    {
        "course_id": "2",
        "course_name": "TOEFL"
    }
    ]

}

Basically i want to add all student properties, student_courses,student_country whithout persisting on course and country table

Upvotes: 3

Views: 3606

Answers (2)

crizzis
crizzis

Reputation: 10716

First of all, the cascade=ALL does the opposite of what you want. ALL=PERSIST,MERGE,REFRESH,DETACH,REMOVE, so it basically says 'whenever I store, update, or delete the Student, do the same with all the associated Courses', among other things.

For @ManyToMany associations, PERSIST, MERGE, REMOVE don't make a lot of sense. You might want to keep REFRESH and DETACH, but you would be well advised to get rid of the rest.

Secondly, removing MERGE has the side effect of getting TransientObjectException. This happens because you are trying to save a detached entity that references other detached entities. You may be calling repository.save(student), but this only merges the Student entity, and the referenced Course entities remain detached.

To solve this problem, you need to replace the detached entity instances with managed entity instances by the same ids:

Set<Courses> managedCourses = new HashSet<>();
for (Course course : stud.getCourses()) {
    managedCourses.add(courseRepository.getOne(course.getId()));
}
stud.setCourses(managedCourses);
studentRepository.save(stud); //no TransientObjectException this time!

(note the use of getOne() in a loop; you may be tempted to use findAllById(), thinking it would be more performant, but the benefit of getOne() is that it does not fetch the associated entity state from the data source. getOne() is provided by JpaRepository specifically with this use case in mind: to establish associations between entities)

Finally, I can see stud is annotated with @RequestBody, suggesting we're in a Controller class here. For the above approach to work, you want to wrap the entire method in a transaction using @Transactional. Since transactional controller methods are not exactly good practice, I'd suggest extracting the body of the studententry method to a separate @Transactional, @Service- annotated bean.

Upvotes: 1

Maciej Kowalski
Maciej Kowalski

Reputation: 26522

You need to switch from @ManyToMany into @OneToMany.

Then you can put the cascading on Student and omit that in the StudentCourse and StudentCountry entities.

The only downside is that you need to create intermediate entites for those linking tables:

Student

@OneToMany(cascade = CascadeType.ALL, mappedBy="student")
private Set<StudentCourse> courses;

StudentCourse

@ManyToOne
private Student student;

@ManyToOne
private Course course;

Course

@Entity
public class Course {
private String courseName;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int courseId;
@ManyToMany(mappedBy = "courses")
private List<StudentCourse> coursestudents;

You would need to alter the incomming json object accordingly to support those intermediate entities ofc.

Upvotes: 0

Related Questions