I have a relationship between Student entity and Publication as One to Many. I need to delete publication from sutdent. When I try to delete publication object but always get the exception:
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [org.irs.entities.GroupStudent#1]
I do not know why it happens. I use Spring MVC 3.2 and Hibernate.
Student entity
#Entity
#org.hibernate.annotations.DynamicUpdate(value = true)
#Table(name = "Student")
public class Student implements Serializable {
public Student() {}
public Student(String studentFullName, String studentBook,
int studentEnter, String studentOKR) {
this.studentFullName = studentFullName;
this.studentBook = studentBook;
this.studentEnter =studentEnter;
this.studentOKR = studentOKR;
}
// create connectivity with table GroupStudent
private GroupStudent groupStudent;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "GroupStudentId")
public GroupStudent getGroupStudent() {
return this.groupStudent;
}
public void setGroupStudent(GroupStudent groupStudent) {
this.groupStudent = groupStudent;
}
// create connectivity with table Publication
private Set<Publication> publications = new HashSet<Publication>();
#OneToMany(mappedBy = "student", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<Publication> getPublications() {
return publications;
}
public void setPublications(Set<Publication> publications) {
this.publications = publications;
}
// other methods
}
Pulication entity
#Entity
#Table(name = "Publication")
public class Publication implements Serializable {
public Publication() {}
public Publication(String publicationTitle, String publicationType,
String publicationPlace, Date publicationDate) {
this.publicationTitle = publicationTitle;
this.publicationType = publicationType;
this.publicationPlace = publicationPlace;
this.publicationDate = publicationDate;
}
// create connectivity with table Student
private Student student;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "StudentId")
public Student getStudent() {
return this.student;
}
public void setStudent(Student student) {
this.student = student;
}
}
GroupStudent entity
#Entity
#Table(name = "GroupStudent")
public class GroupStudent implements Serializable {
public GroupStudent() {}
public GroupStudent(String groupStudentNumber) {
this.groupStudentNumber = groupStudentNumber;
}
// create connectivity with table Student
private Set<Student> students = new HashSet<Student>();
#OneToMany(mappedBy = "groupStudent", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<Student> getStudents() {
return this.students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
}
Controller
#RequestMapping(value = "/deletePublication.html", method = RequestMethod.GET)
public ModelAndView deletePublication(#RequestParam("studentId") Long studentId) {
....
ps.deletePublication(ps.selectPublicationsById(2L));
....
return modelandview;
}
This error happens when you try to fetch entity that was already in the hibernate context. You can't have two attached entities at the same time.
In your controller you firts call ps.selectPublicationsById(2L), that probably causes the error.
Try to replace the delete logicc with HQL or native SQL delete.
String hql = "delete from Publication where Id= :id";
session.createQuery(hql).setString("id", id).executeUpdate();
Related
I have Employee class and Qualification class , I added qualifications of a employee successfully. But ,When i try to update the particular employees qualification by adding one more qualification. I don't have a idea to do.Kindly suggest some view
Employee class
#Entity
#Table(name = "Tbl_Employee")
public class Employee {
private int empId;
private String empName;
private Employee_Address addressDetail;
private List<Employee_Qualification> qualifications;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="EmployeeId", updatable = false, nullable = false)
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
#Column(name="EmployeeName")
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
#OneToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="EmpAdd_FK")
public Employee_Address getAddressDetail() {
return addressDetail;
}
public void setAddressDetail(Employee_Address addressDetail) {
this.addressDetail = addressDetail;
}
#OneToMany(targetEntity=Employee_Qualification.class, mappedBy="employee"
,cascade=CascadeType.ALL, fetch=FetchType.LAZY)
public List<Employee_Qualification> getQualifications() {
return qualifications;
}
public void setQualifications(List<Employee_Qualification> qualifications) {
this.qualifications = qualifications;
}
}
Qualification class
#Entity
#Table (name="Tbl_Employee_Qualification")
public class Employee_Qualification {
private int qualificationId;
private String qualification;
private Employee employee;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="QualificationId", updatable = false, nullable = false)
public int getQualificationId() {
return qualificationId;
}
public void setQualificationId(int qualificationId) {
this.qualificationId = qualificationId;
}
#Column(name="Qualifications")
public String getQualification() {
return qualification;
}
public void setQualification(String qualification) {
this.qualification = qualification;
}
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="Emp_FK")
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
}
Implementation class
// Update Employee and Employee_Qualification from Employee entity class [OnetoManny and ManytoOne bidirectional]
Employee emp =(Employee) session.createQuery("from Employee where empId='10'").uniqueResult();
Employee_Qualification newQ1 = new Employee_Qualification();
newQ1.setQualification("ECE");
List<Employee_Qualification> q1 = emp.getQualifications();
q1.add(newQ1);
emp.setQualifications(q1);
session.save(q1);
session.getTransaction().commit();
When you have a bidirectional relation you need to wire up both sides. In your example you already have this:
q1.add(newQ1);
but you also need to do the reverse binding too:
newQ1.setEmployee(emp)
Just a note : You have Cascade.ALL to both relations (oneToMany and ManyToOne) between your employee and qualification. I haven' t run your code but i am pretty sure is going to create an issue.
You have to decide which entity is responsible to update the other. (i,e if you choose to save the qualifications and the changes to be propagated to employee then remove the cascade from the #oneToMany in the Employee class
So I have two entities with a bi-directional #ManyToMany relation - Student and Course.
My Student class being:
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue
private int id;
#Column(name = "role", unique = true, nullable = false)
private String studentName;
#ManyToMany
#JoinTable(name = "student_course",
joinColumns = {#JoinColumn(name = "student_id",
referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "course_id",
referencedColumnName =
"id")})
private Set<Course> course = new HashSet<>();
public String getStudentName() {
return studentName;
}
public void setStudentName(final String studentName) {
this.studentName = studentName;
}
public Set<Course> getCourses() {
return courses;
}
public void setCourses(final Set<Course> courses) {
this.courses = courses;
}
#Override
public int hashCode() {
...
}
#Override
public boolean equals(final Object obj) {
...
}
#Override
public String toString() {
...
}
My Course class being:
#Entity
#Table(name = "course")
public class Course {
#Id
#GeneratedValue
private int id;
#Column(name = "course", nullable = false, unique = true)
private String course;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "courses")
private Set<Student> students = new HashSet<>();
public String getCourse() {
return course;
}
public void setCourse(final String course) {
this.course = course;
}
public Set<Student> getStudents() {
return students;
}
public void setStudents(final Set<Student> students) {
this.students = students;
}
#Override
public int hashCode() {
...
}
#Override
public boolean equals(final Object obj) {
...
}
#Override
public String toString() {
...
}
Using the hibernate entitymanager I want to persist both the entities. So in my DbInitializer(), I have something like this that fills data for the Course entity.
private Set<Course> initCourses() {
final Set<Course> courses =
CourseProvider.getTestCourses();
for (final Course course : courses) {
entityManager.persist(course);
}
return courses;
}
This works fine. But when I try to fill data for the Student entity, it asks me save the transient instance before flushing.
private List<Student> initStudents() {
final Set<Student> students = StudentProvider.getTestStudents();
for (final Student student : students) {
entityManager.persist(student);
}
return students;
}
StudentProvider class:
public class StudentProvider {
public static List<Student> getTestStudentsList() {
final List<Student> students =
StudentFactory.createStudents();
return students;
}
}
StudentFactory class:
public class StudentFactory {
public static final List<Student> createStudents() {
final EnumSet<StudentEnum> students =
EnumSet.allOf(StudentEnum.class);
final List<Student> studentList = new ArrayList<>();
for (final StudentEnum s : students) {
final Student student = new Student();
student.setStudentName(s.toString());
student.setCourses(s.transform());
studentList.add(student);
}
return studentList;
}
The StudentProvider calls the StudentFactory that sets values for Student.
The StudentEnum has a list of students with the corresponding courses.
I am aware that cascade = CascadeType.ALL could solve the problem. I tried using it but, it gives me a "Duplicate Entry error for the Course entity". And if I don't use the cascade it asks me to save the transient instance.
Could someone please suggest me a way without using cascasde???
I understand that I have to save the Course entity before persisting the Student entity.
I went through a couple of Stackoverflow questions but I couldn't solve the problem. Help needed!
Thank you in advance! :)
is there a chance to fetch join entity with using predicate?
#Entity
public class Student {
#Id
private int id;
private String hobby;
private String favouriteColor;
private String name;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumn(name = "CLASS_ID", referencedColumnName = "ID")
private Classroom classroom;
}
#Entity
public class Classroom {
#Id
private int id;
private String classRoom;
private List<Student> students;
}
I have some predicate
public class StudentPredicate {
private StudentPredicate() {}
public static Predicate createPredicate(final Student filter) {
QStudent student = QStudent.student;
BooleanBuilder builder = new BooleanBuilder();
if (isNotBlank(filter.getHobby())) {
builder.and(student.hobby.eq(filter.getHobby());
}
if (isNotBlank(filter.getFavouriteColor())) {
builder.and(student.favouriteColor.eq(filter.getFavouriteColor()));
}
return builder.getValue();
}
}
and repository
#Repository
public interface StudentRepository extends CrudRepository<Student, Integer>, QueryDslPredicateExecutor<Student> {
}
and now how can I find all students with fetched classrooms?
studentRepository.findAll(predicate)
How to say to query dsl that these findAll should fetch classroom?
As there's FetchType.LAZY for classroom field in Student class, so here while you call getClassRoom() will actually fetch the related entity from db or either you can use FetchType.EAGER.
For some reason I can't delete an object that belongs to a many to many relationship. I get the following error:
Exception in thread "main" org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [edu.cs157b.hibernate.AppointmentRequest#11]
at org.hibernate.internal.SessionImpl.forceFlush(SessionImpl.java:1232)
Here are my three classes that map the many to many relationship. Essentially, Doctor has many Patients through AppointmentRequest & vice versa. Here are the classes
Doctor
package edu.cs157b.hibernate;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.*;
#Entity
#Table(name="DOCTOR_INFO")
#NamedQueries (
{
#NamedQuery(name = "Doctor.getAll", query = "from Doctor"),
#NamedQuery(name = "Doctor.findByName", query = "from Doctor where name = :name")
}
)
public class Doctor implements Person {
private int id;
private String name;
private Specialty specialty;
private List<AppointmentRequest> appointmentRequests = new ArrayList<AppointmentRequest>();
#Id
#GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Column(unique=true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#ManyToOne (fetch = FetchType.EAGER, cascade= CascadeType.PERSIST)
#JoinColumn(name="specialty_id")
public Specialty getSpecialty() {
return specialty;
}
public void setSpecialty(Specialty specialty) {
this.specialty = specialty;
}
#OneToMany(mappedBy="doctor", targetEntity = AppointmentRequest.class,
fetch=FetchType.EAGER, orphanRemoval=true, cascade= CascadeType.ALL)
public List<AppointmentRequest> getAppointmentRequests() {
return this.appointmentRequests;
}
public void setAppointmentRequests(List<AppointmentRequest> appointmentRequests) {
this.appointmentRequests = appointmentRequests;
}
#Transient
public List<Patient> getPatients() {
List<Patient> patients = new ArrayList<Patient>();
for(AppointmentRequest appointment:appointmentRequests) {
patients.add(appointment.getPatient());
}
return patients;
}
}
Patient
package edu.cs157b.hibernate;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.*;
#Entity
#Table(name="PATIENT_INFO")
#NamedQueries (
{
#NamedQuery(name = "Patient.getAll", query = "from Patient"),
#NamedQuery(name = "Patient.findByName", query = "from Patient where name = :name")
}
)
public class Patient implements Person {
private int id;
private String name;
private String medical_record;
private List<AppointmentRequest> appointmentRequests = new ArrayList<AppointmentRequest>();
public String getMedical_record() {
return medical_record;
}
public void setMedical_record(String medical_record) {
this.medical_record = medical_record;
}
#Id
#GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Column(unique=true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#OneToMany(mappedBy="patient", targetEntity = AppointmentRequest.class,
fetch=FetchType.EAGER, orphanRemoval=true, cascade= CascadeType.ALL)
public List<AppointmentRequest> getAppointmentRequests() {
return this.appointmentRequests;
}
public void setAppointmentRequests(List<AppointmentRequest> appointmentRequests) {
this.appointmentRequests = appointmentRequests;
}
#Transient
public List<Doctor> getDoctors() {
List<Doctor> doctors = new ArrayList<Doctor>();
for(AppointmentRequest appointment:appointmentRequests) {
doctors.add(appointment.getDoctor());
}
return doctors;
}
}
ApppointmentRequest
package edu.cs157b.hibernate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.TimeZone;
import javax.persistence.*;
import org.hibernate.annotations.Type;
import java.util.List;
#Entity
#Table(name="APPOINTMENT_REQUEST")
#NamedQueries (
{
#NamedQuery(name = "AppointmentRequest.getAll", query = "from AppointmentRequest"),
#NamedQuery(name = "AppointmentRequest.findByDoctorId", query = "from AppointmentRequest where doctor_id = :doctor_id"),
#NamedQuery(name = "AppointmentRequest.findByPatientId", query = "from AppointmentRequest where patient_id = :patient_id"),
#NamedQuery(name = "AppointmentRequest.findByID", query = "from AppointmentRequest where id = :id")
}
)
public class AppointmentRequest {
private int id;
private Doctor doctor;
private Patient patient;
private boolean fulfilled = false;
private Calendar time;
private final SimpleDateFormat timestampFormat = new SimpleDateFormat("MM/dd/yyyy h a");
public Calendar getTime() {
return time;
}
#Transient
public String getFormattedTime() {
String result = timestampFormat.format(time.getTime());
return result;
}
public void setTime(Calendar time) {
this.time = time;
}
public boolean isFulfilled() {
return fulfilled;
}
public void setFulfilled(boolean fulfilled) {
this.fulfilled = fulfilled;
}
#Id
#GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#ManyToOne (fetch = FetchType.EAGER, cascade= CascadeType.PERSIST)
#JoinColumn(name="doctor_id")
public Doctor getDoctor() {
return doctor;
}
public void setDoctor(Doctor doctor) {
this.doctor = doctor;
}
#ManyToOne (fetch = FetchType.EAGER, cascade= CascadeType.PERSIST)
#JoinColumn(name="patient_id")
public Patient getPatient() {
return patient;
}
public void setPatient(Patient patient) {
this.patient = patient;
}
}
Doctor Delete Method
public void deleteDoctor(String doctor_name) {
Session session = sessionFactory.openSession();
Doctor doctor = new Doctor();
try {
session.beginTransaction();
Query query = session.getNamedQuery("Doctor.findByName");
query.setString("name", doctor_name);
doctor = (Doctor) query.uniqueResult();
if(doctor == null) {
throw new NullPointerException();
}
List<AppointmentRequest> appointments = doctor.getAppointmentRequests();
for(AppointmentRequest appointment:appointments) {
appointment.setDoctor(null);
}
session.delete(doctor);
session.getTransaction().commit();
}
finally {
session.close();
}
}
What this exception really means is you are telling Hibernate to remove object from database but at the same time this object still exist (that means still exist in java or database) in mapped collection via Persistent entity which has CascadeType.PERSIST annotated over it.
It's like having something tied through elastic rubber on the window and then poke it hoping it will drop. Hibernate is smart it is saving you from doing meaningless stuff, it tells you what to do
deleted object would be re-saved
by cascade (remove deleted object from associations)
Sine you are doing appointment.setDoctor(null); it will remove object from collection (only in java as you are not explicitly or implicitly updating appointment).You have CascadeType.PERSIST on doctor that means when hibernate is going to commit the transaction it will find that appointment has association to doctor you just deleted that means if you remove that doctor from table, hibernate has to go and create same doctor as you have not told him to make appropriate changes in appointment as he follows all the entity rules set by you. Since hibernate is smart he knows this and he will throw a exception for you saying don't be an oxymoron and do the right thing.
Now there are more than one solution that I can think of here
Use cascade={CascadeType.PERSIST,CascadeType.REMOVE} or cascade=CascadeType.ALL on getDoctor() in AppointmentRequest
As mentioned in hibernate document here
It doesn't usually make sense to enable cascade on a #ManyToOne or
#ManyToMany association. Cascade is often useful for #OneToOne and
#OneToMany associations.
remove cascade from getDoctor
Since you have FetchType.EAGER on getDoctor() with cascade specified it is little complicated for me interpret the behaviour of hibernate but in this questions they have solved by using FetchType.LAZY am not sure if it will work out for you.
You can do session.saveOrUpdate(appointment) on all the AppointmentRequest which has this doctor and then go for session.delete(doctor);
Hope you this would solve your problem.
I have two mapped types, related many-to-many.
#Entity
#Table(name = "students")
public class Student{
...
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "students2courses",
joinColumns = { #JoinColumn(
name = "student_id",
referencedColumnName = "_id") },
inverseJoinColumns = { #JoinColumn(
name = "course_id",
referencedColumnName = "_id") })
public Set<Course> getCourses() {
return courses;
}
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
...
}
__
#Entity
#Table(name = "courses")
public class Course{
...
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "courses")
public Set<Student> getStudents() {
return students;
}
public void setStudents(Set<Student> students) {
this.students = students;
}
...
}
But if I update/delete Course entity, records are not created/deleted in table students2courses. (with Student entity updating/deleting goes as expected)
I wrote abstract class HibObject
public abstract class HibObject {
public String getRemoveMTMQuery() {
return null;
}
}
which is inherited by Student and Course.
In DAO I added this code (for delete() method):
String query = obj.getRemoveMTMQuery();
if (query != null) {
session.createSQLQuery(query).executeUpdate();
}
and I ovrerided method getRemoveMTMQuery() for Course
#Override
#Transient
public String getRemoveMTMQuery() {
return "delete from students2courses where course_id = " + id + ";";
}
Now it works but I think it's a bad code.
Is there a best way to solve this problem?