I have two classes Student and Subject:
Student
#Entity
#Table(name = "students")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "full_name")
private String fullName;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "students")
private Set<Subject> subject;
//Getters & setters
}
Subject
#Entity
#Table(name = "subjects")
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "subject_name")
private String name;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Student> students;
//Getters & setters
}
When I am trying to save a student registered with more than one subject, I am not getting any records in the JoinTable.
My code for saving the entiers:
Subject subject = new Subject();
subject.setName("JAVA");
Subject subject2 = new Subject();
subject2.setName("C");
Subject subject3 = new Subject();
subject3.setName("C++");
Student student = new Student();
student.setFullName("dibya");
Set<Subject> subjects = new HashSet<>();
subjects.add(subject2);
subjects.add(subject);
subjects.add(subject3);
student.setSubject(subjects);
session.saveOrUpdate(student);
Please tell me where am I doing wrong.
Found the solution to my problem.
It was not with the configuration. With just mappedBy attribute ManyToMany relationship can be accomplished.
In my program I have added session.saveOrUpdate(subject); after saving saving the students.
This solved my problem. :)
A many-to-many relationship is modeled as a join table. This is a table with 2 columns, both are foreign keys to the tables of the repationship (Student and Subject in your case).
In Hibernate if you use #ManyToMany you need to either #JoinTable or #JoinColumn to define the relationship. Once it's done your update will work.
See
http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html/ch07.html
and
http://www.dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
EDIT:
Applied to your code it should look like (UNTESTED):
In Student:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "student_id")
private int id;
....
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "students_subject", joinColumns = { #JoinColumn(name = "student_id") }, inverseJoinColumns = { #JoinColumn(name = "subject_id") })
private Set<Subject> subject;
In Subject
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "subject_id")
private int id;
...
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "students_subject", joinColumns = { #JoinColumn(name = "subject_id") }, inverseJoinColumns = { #JoinColumn(name = "student_id") })
private Set<Student> students;
Your case might be falling into mixing JPA and Hibernate cascading. Try annotate your #ManyToMany with additional
#Cascade({CascadeType.SAVE_UPDATE})
you can also consult this two links to check if this is really your case:
Confusion between JPA and Hibernate cascading
http://www.mkyong.com/hibernate/cascade-jpa-hibernate-annotation-common-mistake/
Related
I have two Entities in my Spring-Boot mongoDb Application:
Course.java
#Entity
#Table(name = "students")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
String title;
#Enumerated(EnumType.STRING)
OptionCourse optionCourse;
private double fee;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "courses")
private Set<Student> student = new HashSet<>();
}
and
Student.java
Entity
#Table(name = "students")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
int age;
String department;
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "student_courses",
joinColumns = { #JoinColumn(name = "student_id") },
inverseJoinColumns = { #JoinColumn(name = "course_id") })
private Set<Course> courses = new HashSet<>();
}
for my mongoDb database:
I want to realise a Many-to-Many-Relationship between both Entities. Every courses should be able to assign multiple student.
How can i replace my annotations (#Entity, #Table,#Id, #Enumerated(EnumType.STRING) and #ManyToMany(fetch = FetchType.LAZY,...)) in my entities. I use spring-boot-starter-data-mongodb in my maven dependencies
In the following example, there are 3 entities which have relations e.g. #ManyToMany, #OneToMany and #ManyToOne:
Student:
#Entity
#Data
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
private String name;
#JsonIgnore
#ManyToMany(mappedBy = "students")
private Set<Subject> subjects = new HashSet<>();
}
Subject:
#Entity
#Data
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
private String name;
#ManyToMany
#JoinTable(
name = "subject_student",
joinColumns = #JoinColumn(name = "subject_id"),
inverseJoinColumns = #JoinColumn(name = "student_id")
)
Set<Student> students = new HashSet<>();
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "teacher_id", referencedColumnName = "id")
private Teacher teacher;
}
Teacher:
#Entity
#Data
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#JsonIgnore
#OneToMany(mappedBy = "teacher")
private Set<Subject> subjects;
}
1. In the subject entity, I tried to remove #JoinColumn and the related entities are connected as the example above:
#ManyToMany
#JoinTable(name="subject_student")
public Set<Student> students = new HashSet<>();
#ManyToOne(cascade = CascadeType.ALL)
private Teacher teacher;
So, if we want to use subject_id - student_id pair in subject_student table and use teacher_id in subject table as it is created in the example, can I use my simplified notation by removing #JoinColumn? Because, if there is not a special case, I think it is redundant to verbose notation of relations.
2. When I use the second approach, the columns are created as plural e.g. subjects_id - students_id in subject_student. So, can I prevent this and create them as in the previous example by using my approach?
I have a confusing problem which I haven't figured out how to solve. if you can offer a suggestion of how I can fix my problem I would be grateful.
So I have the following entity relationship model here.
The mapping of User.class is:
#Entity
#Table(name = "CRM_USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USER_ID")
private Long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "BIRTHDATE")
private Date birthDate;
#Column(name = "EMAIL")
private String email;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private UserAdditionalInfo additionalInfo;
#ManyToOne
#JoinColumn(name = "TEAM_FK")
private Team team;
#ManyToOne
#JoinColumn(name = "JOB_FK")
private Job job;
#ManyToOne
#JoinColumn(name = "ORGANIZATION_FK", nullable = false)
private Organization organization;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Security security;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "INFO_FILE_FK")
private InfoFile profilePicture;
#ManyToOne
#JoinColumn(name = "COUNTRY_FK")
private Country country;
// Getters and Setters
}
The mapping of Comment.class is:
#Entity
#Table(name = "CRM_COMMENT")
public class Comment implements Serializable {
private static final long serialVersionUID = -104145851368148154L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "COMMENT_ID")
private Long id;
#ManyToOne
#JoinColumn(name = "ARTICLE_ID")
private Article article;
#ManyToOne
#JoinColumn(name = "USER_FK", nullable = false)
private User createdUser;
#Column(nullable = false)
private String comment;
#Column(name = "CREATION_DATE", nullable = false)
private Date creationDate;
#Column(name = "MODIFICATION_DATE")
private Date modificationDate;
#OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true)
#JoinTable(name = "CRM_COMMENT_LIKE",
joinColumns = {#JoinColumn(name = "COMMENT_ID")},
inverseJoinColumns = {#JoinColumn(name = "USER_ID")})
private Set<User> fans = new LinkedHashSet<>();
// Getters and Setters
}
The mapping of Article.class is:
#Entity
#Table(name = "CRM_ARTICLE")
public class Article implements Serializable {
// other properties
#OneToMany(mappedBy = "article", cascade = {CascadeType.ALL}, orphanRemoval = true)
#OrderBy("id DESC")
private Set<Comment> comments = new LinkedHashSet<>();
// Getters and Setters
}
The problem is related to my ManyToMany relation between the Comment and User - CRM_COMMENT_LIKE.
Actually, when I add some new 'fan' into Comment, there is no problem.
#Override
public boolean giveAnLikeToComment(Long commentId, User fan) {
Comment comment = commentDao.get(commentId);
if (Objects.isNull(comment)|| BooleanUtils.isTrue(comment.getFans().contains(fan))) {
return false;
}
comment.getFans().add(fan);
commentDao.update(comment);
return true;
}
The problem arises when I try to delete some comment, which has at least one 'like'/'fan' to it.
#Override
public boolean deleteCommentById(final Long commentId) {
Comment comment = commentDao.get(commentId);
if (Objects.nonNull(comment)) {
Article article = comment.getArticle();
article.getComments().remove(comment);
comment.setFans(null); // This line fix the problem
articleDao.update(article);
return true;
}
return false;
}
So in this case, I manage the relation between an Article ( which is parent of a Comment) and the comment itself. This is easy, because the connection between them is bidirectional. But what about the fans? I can't remove the connection between a Comment and CRM_COMMENT_LIKE relation, because the User doesn't know about the CRM_COMMENT_LIKE or about the Comments. Something more, I want, when I remove a Comment, to remove and all created relations in CRM_COMMENT_LIKE. But I'm prevent, because Hibernate throws an exception which says:
deleted object would be re-saved by cascade (remove deleted object from associations):
[crm.alltogether.core.admin.model.User#1]; nested exception is
org.hibernate.ObjectDeletedException: deleted object would be re-saved
by cascade (remove deleted object from associations):
[crm.alltogether.core.admin.model.User#1]
This is my issue, so if you have a suggestion, I would be glad to read it :)
Best Regards,
You need to have a orphanRemoval=true cascading between Article and Comment. Then you would do this.
if (Objects.nonNull(comment)) {
Article a = comment.getArticle();
a.getComments().remove(comment);
articleDao.saveOrUpdate(a);
return true;
}
This will take care of deleting orphan comment, as you already have a cascade all on fans it would delete that association as well. I would suggest you to play around with cascade orphanRemoval=true on fans as well.
I have an existing database modeled the following way:
Student - SchoolId(PK), StudentId(PK), StudentName
Teacher - SchoolId(PK), TeacherId(PK), TeacherName
Student_Teacher - SchoolId(PK), StudentId(PK), TeacherId(PK)
Foreign key references exist from Student_Teacher to respective entities.
Now I am creating hibernate entities for this existing database. And I am running into weird issues creating Many-to-Many mapping from Student to Teacher.
#Entity
#Table(name = "Student")
public class Student {
#EmbeddableId
private StudentPK itemId;
#Column(name="StudentName")
private String studentName;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="Student_Teacher", joinColumns={#JoinColumn(name="SchoolId", referencedColumnName="SchoolId"),#JoinColumn(name="StudentId", referencedColumnName="StudentId")}, inverseJoinColumns={#JoinColumn(name="SchoolId", referencedColumnName="SchoolId"),#JoinColumn(name="TeacherId", referencedColumnName="TeacherId")})
private List<Teacher> attachments=new ArrayList<Teacher>();
}
The above code compains about some duplicate SchoolId reference.
Any ideas?
As I see that there is an issue in your mapping of entities, It should be as follows
school - school_id(PK), school_name
student - student_id(PK) , student_name, fk_school_id(FK),
teacher - teacher_id(PK), teacher_name , fk_school_id(FK)
*student_teacher* - student_teacher_id(PK), fk_student_id(FK), fk_teacher_id(FK)
and Entity clasess as follows
School Entity
#Entity
#Table(name = "school")
public class School {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column (name = "school_id")
private int Id;
#Column(name="school_name")
private String schoolName;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "school")
private Set<Student> students = new HashSet<Student>
#OneToMany(fetch = FetchType.LAZY, mappedBy = "school")
private Set<Teacher> teachers = new HashSet<Teacher>
}
Student Entity
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column (name = "student_id")
private int Id;
#Column(name="student_name")
private String studentName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "school_id", nullable = false)
private School school;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "student_teacher", joinColumns = {#JoinColumn(name = "fk_student_id") }, inverseJoinColumns = { #JoinColumn(name = "fk_teacher_id") })
private List<Teacher> teachers = new ArrayList<Teacher>();
}
Teacher Entity
#Entity
#Table(name = "teacher")
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column (name = "teacher_id")
private int Id;
#Column(name="teacher_name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "school_id", nullable = false)
private School school;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "student_teacher", joinColumns = {#JoinColumn(name = "fk_teacher_id") }, inverseJoinColumns = { #JoinColumn(name = "fk_student_id") })
private List<Student> students =new ArrayList<Student>();
}
hope this will solve this problem..
as you have declare 'SchoolId' as PK in Student_Teacher table it will not allow you to add duplicate entry for SchoolId field for Student_Teacher table and this is not the case. thus the above relationship will gives duplicate SchoolId reference. when you are going to add two different students from same school into Student_Teacher table..
Did you define the various PKs per entity as compound keys, as i note that you use multiple PKs per entity. Is there any constraint why you can't use a sole PK per entity and just use a relation table to bind the 2 entities?
I have the following entities
Student
#Entity
public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//getter and setter for id
}
Teacher
#Entity
public class Teacher implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
//getter and setter for id
}
Task
#Entity
public class Task implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false)
#JoinTable(name = "student_task", inverseJoinColumns = { #JoinColumn(name = "student_id") })
private Student author;
#ManyToOne(optional = false)
#JoinTable(name = "student_task", inverseJoinColumns = { #JoinColumn(name = "teacher_id") })
private Teacher curator;
//getters and setters
}
Consider that author and curator are already stored in DB and both are in the attached state. I'm trying to persist my Task:
Task task = new Task();
task.setAuthor(author);
task.setCurator(curator);
entityManager.persist(task);
Hibernate executes the following SQL:
insert
into
student_task
(teacher_id, id)
values
(?, ?)
which, of course, leads to null value in column "student_id" violates not-null constraint
Can anyone explain this issue and possible ways to resolve it?
UPDATE
See my own solution below.
I've resolved my issue with the help of #SecondaryTable and switched from #JoinTable to #JoinColumn:
Task
#Entity
#SecondaryTable(name="student_task")
public class Task implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(optional = false)
#JoinColumn(table = "student_task", name = "student_id")
private Student author;
#ManyToOne(optional = false)
#JoinColumn(table = "student_task", name = "teacher_id")
private Teacher curator;
//getters and setters
}
Now, generated SQL looks like:
insert
into
student_task
(student_id, teacher_id, id)
values
(?, ?, ?)
and everything works just fine :)
I think you are missing the JoinColumns tag...
joinColumns = { #JoinColumn(name = "student_id", referencedColumnName = "id") }
joinColumns = { #JoinColumn(name = "teacher_id", referencedColumnName = "id") }
in author and curator respectively
Also remember, that the inversjoincolumn is the column in the owned table.. so it must be something like:
inverseJoinColumns = {#JoinColumn(name="id")})