Reputation: 33
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
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
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