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! :)
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
I read https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/.
I tried suggestion config like(using spring data JPA,hibernate 5.0 as vendor ):
public class PaperSubjectType{
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private PaperSetting paperSetting;
..
}
class PaperSetting{
#Id
#GeneratedValue
private Long id;
..
}
first I tried the example:
PaperSetting paperSettingInDb = paperSettingRepository.findOne(1);
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setSubjectCode("91");
paperSubjectType.setPaperSetting(paperSettingInDb);
paperSubjectTypeRepository.save(paperSubjectType);
error:detached entity passed to persist:PaperSetting.
it seems hibernate take PaperSetting as detached when cascade
2 if I want to create both PaperSubjectType and PaperSetting together,do I need to do this:
PaperSetting paperSetting = new PaperSetting();
paperSetting.setxx;
PaperSetting paperSettingInDbNew = paperSettingRepository.save(paperSetting);
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setPaperSetting(paperSettingInDbNew);
paperSubjectTypeRepository.save(paperSubjectType);
or I should use bidirectional in this situation?
thank you!
I think you may have forgotten to wrap the logic in a #Transactional block
#Transactional
PaperSetting paperSettingInDb = paperSettingRepository.findOne(1);
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setSubjectCode("91");
paperSubjectType.setPaperSetting(paperSettingInDb);
paperSubjectTypeRepository.save(paperSubjectType);
without that crudRepository.findOne() will open it's own short lived transaction so when you get the return of findOne() the entity is already detached, hence the error
I tried it Hibernate 5.2 and it works like a charm.
Assuming you have these entities:
#Entity(name = "Person")
public static class Person {
#Id
#GeneratedValue
private Long id;
#NaturalId
private String registrationNumber;
public Person() {}
public Person(String registrationNumber) {
this.registrationNumber = registrationNumber;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRegistrationNumber() {
return registrationNumber;
}
}
#Entity(name = "PersonDetails")
public static class PersonDetails {
#Id
private Long id;
private String nickName;
#OneToOne
#MapsId
private Person person;
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
}
And this data access logic:
Person _person = doInJPA( this::entityManagerFactory, entityManager -> {
Person person = new Person( "ABC-123" );
entityManager.persist( person );
return person;
} );
doInJPA( this::entityManagerFactory, entityManager -> {
Person person = entityManager.find( Person.class, _person.getId() );
PersonDetails personDetails = new PersonDetails();
personDetails.setNickName( "John Doe" );
personDetails.setPerson( person );
entityManager.persist( personDetails );
} );
The test passes just fine in Hibernate ORM.
Maybe it was a bug in 5.0 that got fixed, so you are better of upgrading.
1) Add cascading option:
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#MapsId
private PaperSetting paperSetting;
2) Having that in place, you can save only PaperSubjectType while creating both entities anew:
PaperSetting paperSetting = new PaperSetting();
paperSetting.setxx;
PaperSubjectType paperSubjectType = new PaperSubjectType();
paperSubjectType.setPaperSetting(paperSettingInDbNew);
paperSubjectTypeRepository.save(paperSubjectType);
i faced the same problem as empty tables when using many to many relations in jpa. Sadly this post was without solution. I have a class with a many-to-many relation in JPA/EclipseLink v2.6.3. Here the class ROLE:
#Entity
public class Role implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
#JoinTable(name = "ROLE_COMPETENCE", joinColumns = #JoinColumn(name = "ROLE_ID", referencedColumnName = "ID"), inverseJoinColumns = #JoinColumn(name = "COMPETENCE_ID", referencedColumnName = "ID"))
private List<Competence> competences = new ArrayList<Competence>();
#Override
public String toString() {
return "Role [id=" + id + "]";
}
public void addCompetence(Competence competence) {
if (this.competences != null) {
if (!this.competences.contains(competence)) {
this.competences.add(competence);
competence.addRole(this);
}
}
}
public int getId() {
return id;
}
public Role() {
super();
}
public List<Competence> getCompetences() {
return competences;
}
}
And the class COMPETENCE
#Entity
public class Competence implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(length = 1024, nullable = false)
private String title;
#JsonIgnore
#ManyToMany(mappedBy = "competences", fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
private List<Role> roles;
public int getId() {
return id;
}
public Competence() {
super();
}
public void addRole(Role role) {
if (this.roles != null) {
if (!this.roles.contains(role)) {
this.roles.add(role);
role.addCompetence(this);
}
}
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<Role> getRoles() {
return roles;
}
#Override
public String toString() {
return "Competence [id="+id+", title="+title+"]";
}
}
When i now use
for (int i = 0; i < 10; i++) {
Role role = new Role();
RoleService.create(role);
for (int j = 0; j < 3; j++) {
Competence c = new Competence();
c.setTitle("Competence "+i+"."+j);
CompetenceService.create(c);
lastCompetence = c;
role.addCompetence(c);
}
RoleService.update(role);
}
with
public void x(Object object) {
EntityManager em = ...
EntityTransaction tx = em.getTransaction();
tx.begin();
em.merge(object); //for x=update or em.persist(object) for x=create
tx.commit();
em.close();
}
The strange thing is that the join-table ROLE_COMPETENCE is correct when i add or remove objects. When i load ROLE's they have competences an can be shown. But the list "roles" is empty when i load competences from the database.
Any idea?
I found my mistake. It was just that the list was not instantiated. With List<Role> roles = new ArrayList<Role>(); everything works fine.
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();
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?