One to many relationship without reference in Hibernate - java

I have a one to many relationship as you see here. What I want to do is I want to save school object with student objects without setting schools of student objects. Below code works but hibernate inserts null values to school_id_fk column.
public class Student {
....
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "school_id_fk")
private School school;
}
public class School{
....
#OneToMany(cascade = CascadeType.ALL, mappedBy = "school")
private Set<Student> studentSet = new HashSet<Student>(0);
}
and main method;
School school = new School();
school.setSchoolName("school name");
Student student = new Student();
student.setStudentName("studentname1");
student.setStudentSurname("studentsurname1");
//student.setSchool(school); I don't want to set this line
Student student2 = new Student();
student2.setStudentName("studentname2");
student2.setStudentSurname("studentsurname2");
//student2.setSchool(school); I don't want to set this line
SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
school.getStudentSet().add(student);
school.getStudentSet().add(student2);
session.save(school);
session.getTransaction().commit();
session.close();
sessionFactory.close();

#Funtik method is correct, however it needs third table.
But your approach is also correct. You can have this relation bidirectional.
If you want to have your school_id_fk column not null you can use
a common pattern in hibernate with (so called) convenience method:
public class Student {
....
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "school_id_fk")
private School school;
}
public class School{
....
#OneToMany(cascade = CascadeType.ALL, mappedBy = "school")
private Set<Student> studentSet;
// this is private setter (used by hibernate internally)
private void setStudentSet(Set<Student> studentSet) {
this.studentSet = studentSet;
}
// this is public method (exposed API)
public void addStudent(Student) {
if (studentSet == null) {
studentSet = new HashSet<>();
}
student.setSchool(this);
studentSet.add(student);
}
}
As you can see (according to this pattern) you should hide studentSet as only hibernate should use setStudentSet() setter.
Private setter will do that. But you can expose public API to operate on this set - addStudent() in this case.
In addStudent(student) method student object is added to set and assigned with parent school in encapsulated way.
Summary: this is common pattern when using hibernate, hide collection setter, expose convenience methods like addStudent().
In this approach you have your FK column always filled, and you can fetch School object from Student object without HQL query. There is no need for third table in this case.
This is just alternative to #Funtik solution.

MappedBy is not needed in school entity
public class School{
....
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "school_id")
private Set<Student> studentSet = new HashSet<Student>(0);
and don't use school in the Student entity
public class Student {
....
}

Related

Why the Cascading persist does not work with many-to-many relationship in Hibernate?

Please look at the following code snippets from 2 classes (Entities) Student and Course
public class Student {
...
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "course_student",
joinColumns = #JoinColumn(name="student_id"),
inverseJoinColumns = #JoinColumn(name="course_id")
)
private List<Course> courses;
...
}
public class Course {
...
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "course_student",
joinColumns = #JoinColumn(name="course_id"),
inverseJoinColumns = #JoinColumn(name="student_id")
)
private List<Student> students;
...
}
and the driver code is as follows
try {
session.beginTransaction();
Course course = new Course("Ukulele master class");
Student student1 = new Student("Jishnu","M V","jishnumv#gmail.com");
Student student2 = new Student("Praveen","M V","praveenmv#gmail.com");
course.add(student1);
course.add(student2);
session.save(course);
session.getTransaction().commit();
}
When I run this code I am getting the following exception
Exception in thread "main" java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.jithin.entity.Student
So my doubt is. persisting the Course does not persist the associated Student(s) even though we have mentioned CascadeType.ALL in the Course class. So why the cascading is not working in the case of many to many?
nb: When I saved both the student objects using session.save(), before saving the course object. There were no exception.
You should use #JoinTable annotation only on the owning side of the #ManyToMany association.
#Entity
public class Student {
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "course_student",
joinColumns = #JoinColumn(name="student_id"),
inverseJoinColumns = #JoinColumn(name="course_id"))
private List<Course> courses;
// ...
}
#Entity
public class Course {
#ManyToMany(mappedBy = "courses", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Student> students;
// ...
}
If you use bidirectional association you should take care about synchronization of both sides of association. So, you should add the following methods to your Course entity:
public class Course {
public void addStudent(Student student) {
students.add(student);
student.getCourses().add( this );
}
public void removeStudent(Student student) {
students.remove(student);
student.getCourses().remove( this );
}
// ...
}
and then you will be able to do something like this:
Course course = new Course("Ukulele master class");
Student student1 = new Student("Jishnu","M V","jishnumv#gmail.com");
Student student2 = new Student("Praveen","M V","praveenmv#gmail.com");
course.addStudent(student1);
course.addStudent(student2);
session.save(course);
As it mentioned in the documentation:
For #ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException.
That is why you should avoid to use cascade = CascadeType.ALL in the #ManyToMany.

JPA: OneToMany relationship keeps the Collection empty

It seems I am having a difficult time understanding JPA and how the OneToMany relationship actually works.
For example, imagine I have an object Class
#Entity
public class Class {
#Id
private String className;
#OneToMany(cascade = Cascade.ALL, orphanRemoval = true)
private Set<Student> students;
// Constructors, Getters, Setter
}
I also have an object Student where it holds Class.
#Entity
public class Student {
#Id
private String studentName;
#ManyToOne
private Class class;
// Constructors, Getters, Setter
}
Obviously, a student can have multiple classes but forget about that.
Why is that when I build a class and then build a couple students using that class, findAll() on the ClassRepository returns me an empty set of students.
Class class = new Class("CS", new HashSet<>());
classRepository.save(class); // repository has no special functions
Student student1 = new Student("1", class);
Student student2 = new Student("2", class);
studentRepository.save(student1);
studentRepository.save(student2);
classRepository.findAll() // Returns me a List<Class> with only one class object that has an empty set.
I was thinking the above code should automatically see that the two students are from that one class and so when I call buildingRepository.findAll(), it will return a Class object with the students set populated properly.
Is my understanding wrong then? Or is my code wrong? And how can I change it up to fix it?
You can choose:
1. Unidirectional #OneToMany:
#Entity
public class Class {
#Id
private String className;
#OneToMany(cascade = Cascade.ALL, orphanRemoval = true)
private List<Student> students=new ArrayList<>();
// Constructors, Getters, Setter
}
#Entity
public class Student {
#Id
private String studentName;
// Constructors, Getters, Setter
}
Now, if we persist one Class:
Class class1=new Class("name1");
class1.getStudents().add(new Student("student1Name"));
// then you can make a save of class1 in db
classRepository.save(class);
2. Unidirectional #OneToMany with #JoinColumn:
To fix the aforementioned extra join table issue, we just need to add the #JoinColumn in the mix:
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "class_id")
private List<Student> students = new ArrayList<>();
3. Bidirectional #OneToMany:
The best way to map a #OneToMany association is to rely on the #ManyToOne side to propagate all entity state changes:
#Entity
public class Class {
#Id
private String className;
#OneToMany(
mappedBy = "class",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Student> students=new ArrayList<>();
// Constructors, Getters, Setter
public void addStudent(Student student) {
students.add(student);
student.setClass(this);
}
public void removeStudent(Student student) {
students.remove(student);
student.setClass(null);
}
}
#Entity
public class Student {
#Id
private String studentName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "class_id")
private Class class;
}
And to persist:
Class c1=new Class("className1");
c1.addStudent(new Student("StudentNAme1"));
c1.addStudent(new Student("StudentNAme2"));
c1.addStudent(new Student("StudentNAme3"));
classRepository.save(c1);

Hibernate delete silently failing

I am using Java+JPA+Hibernate+Playframework1 in a web application and have models Classroom, Student and Group (see below). Each model has exactly one instance in database, and those instances are related to each other. Ideally, deleting a classroom should also delete it's groups, and dissociate it's students, deleting a student should dissociate it's classrooms and groups, and deleting a group should dissociate it's students, but it is not working.
|Classroom|[Many]-------[Many]|Student|
[One] [Many]
| /
| /
[Many] /
|Group|[Many]--------------´
When I try to delete a classroom, a student or a group they are simply not deleted. No runtime exception, no SQL error, no nothing. It just fails silently. I even activated sql debug and didn't see any delete, just selects.
Update: Somehow, courses with groups not related to students are deleted correctly.
Here are mappings:
#Entity(name = "r_classroom")
public class Classroom extends Model {
#OneToMany(mappedBy = "classroom", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Group> groups = new ArrayList<>();
#ManyToMany(mappedBy = "classrooms", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public List<Student> students = new ArrayList<>();
public String name;
}
#Entity(name = "r_student")
public class Student extends Model {
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public List<Classroom> classrooms = new ArrayList<>();
#ManyToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
public List<Group> groups = new ArrayList<>();
public String name;
}
#Entity(name = "r_group")
public class Group extends Model {
#ManyToOne
public Classroom classroom;
#ManyToMany(mappedBy = "groups", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public List<Student> students = new ArrayList<>();
public String name;
}
The Model class is part of Play Framework. I am using Play 1.4.4.
I believe something is wrong with my mappings, but what?
I posted the source code at github.

Do bidirectional entities require both sides to add each other in Hibernate?

Do bidirectional entities (OneToMany, ManyToMany) require both sides to add each other to be saved correctly in Hibernate? From my experience, they are required. Just trying to confirm my understanding.
That is, for the entitles below, are the indicated lines required?
Student student = new Student("Carl");
Course course = new Course("Science");
ReportCard reportCard = new ReportCard("A");
student.getCourses().add(course);
student.getReportCards().add(reportCard);
reportCard.setStudent(student); // <-- Is this required?
course.getStudents().add(student); // <-- Is this required?
studentRepository.save(student);
Student.java
#Entity
public class Student {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "student")
private List<ReportCard> reportCards = new ArrayList<ReportCard>();
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(joinColumns = #JoinColumn(name = "student_id"), inverseJoinColumns = #JoinColumn(name = "course_id"))
private List<Course> courses = new ArrayList<Course>();
#Column
private String name;
}
ReportCard.java
#Entity
public class ReportCard {
#ManyToOne(optional = false)
#JoinColumn(nullable = false)
private Student student;
#Column
private String grade;
}
Course.java
#Entity
public class Course {
#ManyToMany(mappedBy = "courses")
public List<Student> students = new ArrayList<>();
#Column
private String name;
}
EDIT: Replaced #JoinColumn with #Column
Hibernate (ad other JPA implementations) cares about the owning side of the association. The owning side is the side which doesn't have the mappedBy attribute. So, in your example, reportCard.setStudent(student);is required, but student.getReportCards().add(reportCard); is not.
But in general, it's indeed best to set each side of the association correctly, to have a coherent graph of objects and simply avoid bugs in your code.
Note that annotating your String fields with JoinColumn is wrong. They're not join columns, but columns. So you should annotate them with #Column, although it's also useless if you don't specify any attribute of the annotation.
In addition to the above answer, it can be helpful to manage this in the setters. In ReportCard.java
public void setStudent(Student student) {
this.student = student;
student.setReportCard(this);
}
And in Student.java:
void setReportCard(ReportCard reportCard) {
this.reportCard = reportCard;
}
So then using reportCard.setStudent(student); will set both sides for you.

How to insert ManyToOne relationship record in hibernate?

I have two classes as following,
Human.java
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Human implements Serializable {
private long id;
private String name;
....
}
Student.java
#Entity
#DynamicUpdate
public class Student extends MyFactories {
private List<Know> KnowList;
#OneToMany(fetch = FetchType.LAZY)
public List<Know> getKnowlist() {
return knowlist;
}
public void setKnowlist(List<Know> KnowList) {
return Knowlist;
}
}
Know.java
#Entity
public class Know implements Serializable {
private long id;
private Human hu;
private Student st;
....
#ManyToOne
public Person getHu() {
return hu;
}
#ManyToOne
public Client getSt() {
return st;
}
.... setters .....
}
Code
Know kw = new Know();
kw.setSt(studentObject);
kw.setHu(humanObject);
session.save(kw);
tx.commit();
I am able to insert into Know table but hibernate does not insert any record to student_know table which it has created.
I have found this answer but it says I need to use that method if I always want to retrieve all the records. Which I do not (at times, I may just need to retrieve the student class not list of its know)
System.out.println(this.student.getKnowList().size());
When I try to access the list it runs into following exception.
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.myproject.Student.knowList, could not initialize proxy - no Session
for select case change that #OneToMany(fetch = FetchType.LAZY) to #OneToMany(fetch = FetchType.EAGER) so you can get data inside it's list.
and for the insert i need your clarification about where is your relation or getter setter of the private Factory fac;?
you should have at least something like this :
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "YOUR_FACTORY_ID_COLUMN")
private Factory fac;
public Factory getFac(){
return fac;
}
public void setFac(Factory fac){
this.fac=fac;
}
and did factory have any id?
You need to use session.Update(studentObject) as well, to insert a row into student_know table.
Please also be aware that access to a lazy association outside of the context of an open Hibernate session will result in an exception. Link

Categories

Resources