Reputation: 51
I was trying to create a Library management system for that I have created two entities Student
and Books
both are connected using the @ManytoMany
relation so I had used another join table where I store the Id of the book and Id of the student when a student Issues a book from Library but when the student returns the book the row contains both the ID's in the join table should be deleted but When I am trying to do so Either all the data from the Join table get deleted or my student and book got deleted
Here is my code for that Please suggest to me what I am doing wrong here and what should be the best practice
Student Entity
package com.Library.LibraryManagement.Entity;
import java.sql.Timestamp;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name="student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private Integer id;
@Column(name="first_name")
private String firstName;
@Column(name="last_name")
private String lastName;
@Column(name="student_email")
private String email;
@Column(name="student_course")
private Integer course;
@Column(name="date_of_birth")
private String dateOfBirth;
@Column(name="student_status")
private Integer status;
@Column(name="added_at")
private Timestamp addedAt;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name="student_books",
joinColumns=@JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name="books_id"))
private List<Books> books;
public Student() {
//Empty Constructor
}
public Student(String firstName, String lastName, String email, Integer course,String dateOfBirth,Integer status,Timestamp addedAt) {
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.course = course;
this.dateOfBirth = dateOfBirth;
this.status = status;
this.addedAt = addedAt;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getCourse() {
return course;
}
public void setCourse(Integer course) {
this.course = course;
}
public String getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(String dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Timestamp getAddedAt() {
return addedAt;
}
public void setAddedAt(Timestamp addedAt) {
this.addedAt = addedAt;
}
public List<Books> getBooks() {
return books;
}
public void setBooks(List<Books> books) {
this.books = books;
}
public void removeBook(Books boo) {
this.books.remove(boo);
boo.getStudents().remove(this);
}
@Override
public String toString() {
return "{\"id\":\"" + id + "\", \"firstName\":\"" + firstName + "\", \"lastName\":\"" + lastName
+ "\", \"email\":\"" + email + "\", \"course\":\"" + course + "\", \"dateOfBirth\":\"" + dateOfBirth
+ "\", \"status\":\"" + status + "\", \"addedAt\":\"" + addedAt + "\"}";
}
}
Books Entity
package com.Library.LibraryManagement.Entity;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
@Entity
@Table(name="books")
public class Books {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private Integer id;
@Column(name="book_name")
private String bookName;
@Column(name="book_publisher")
private String bookPublisherName;
@Column(name="book_description")
private String bookDescription;
@Column(name="book_language")
private String bookLanguage;
@Column(name="book_in_stock")
private Integer bookInStock;
@Column(name="added_at")
private Timestamp addedAt;
@Column(name="deleted")
private Integer deleted;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name="student_books",
joinColumns=@JoinColumn(name = "books_id"),
inverseJoinColumns = @JoinColumn(name="student_id"))
private List<Student> students;
public Books() {
}
public Books(String bookName, String bookPublisherName, String bookDescription, String bookLanguage,Timestamp addedAt,
Integer bookInStock,Integer deleted) {
this.bookName = bookName;
this.bookPublisherName = bookPublisherName;
this.bookDescription = bookDescription;
this.bookLanguage = bookLanguage;
this.bookInStock = bookInStock;
this.addedAt =addedAt;
this.deleted = deleted;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getBookPublisherName() {
return bookPublisherName;
}
public void setBookPublisherName(String bookPublisherName) {
this.bookPublisherName = bookPublisherName;
}
public String getBookDescription() {
return bookDescription;
}
public void setBookDescription(String bookDescription) {
this.bookDescription = bookDescription;
}
public String getBookLanguage() {
return bookLanguage;
}
public void setBookLanguage(String bookLanguage) {
this.bookLanguage = bookLanguage;
}
public Integer getBookInStock() {
return bookInStock;
}
public void setBookInStock(Integer bookInStock) {
this.bookInStock = bookInStock;
}
public Timestamp getAddedAt() {
return addedAt;
}
public void setAddedAt(Timestamp addedAt) {
this.addedAt = addedAt;
}
public Integer getDeleted() {
return deleted;
}
public void setDeleted(Integer deleted) {
this.deleted = deleted;
}
public List<Student> getStudents() {
return students;
}
public void setStudents(List<Student> students) {
this.students = students;
}
public void removeBook(Student stu) {
this.students.remove(stu);
stu.getBooks().remove(this);
}
@Override
public String toString() {
return "{\"id\":\"" + id + "\", \"bookName\":\"" + bookName + "\", \"bookPublisherName\":\"" + bookPublisherName
+ "\", \"bookDescription\":\"" + bookDescription + "\", \"bookLanguage\":\"" + bookLanguage
+ "\", \"bookInStock\":\"" + bookInStock + "\", \"addedAt\":\"" + addedAt + "\", \"deleted\":\""
+ deleted + "\"}";
}
//Convience MEthods to help with adding the student
public void addStudent(Student theStudent) {
if(students ==null) {
students = new ArrayList<>();
}
students.add(theStudent);
}
}
Method used to Revome Data from Join table
@Override
@Transactional
public void returnBok(Student student, Books book) {
try {
if(student !=null && book !=null) {
// Student stu = book.getStudents(.);
// book.removeBook(student);
for(Books books : student.getBooks()) {
student.removeBook(book);
}
entityManage.remove(student);
}
}catch(Exception e) {
e.printStackTrace();
}
}
Upvotes: 0
Views: 2733
Reputation: 8206
That's because you are using entityManager.remove
. That command will delete a student and all the books associated to it because of the cascade.
If you only want to remove a book from the association,the returnBook
method should look something like this:
@Transactional
public void returnBook(Student student, Books book) {
try {
if(student !=null && book !=null) {
Student entityStudent = entityManager.getReference( Student.class, student.getId())
Book entityBook = entityManager.getReference( Book.class, book.getId())
entityStudent.removeBook(entityBook);
}
}
...
}
Changes will be propagated to the db during commit or the flush of the session.
Note that I've added the getReference()
calls because I don't know if student
and book
are managed entities. If they are, it's not necessary to use getReference()
.
Assuming that this is what removeBook
looks like:
public void removeBook(Book book) {
this.books.remove( book );
book.getStudents().remove( this );
}
As described in the Hibernate ORM documentation, it's important to update both side of the association.
Also the mapping of the association on one of the entities should be:
@ManyToMany(mappedBy = "...")
You should decide which entity owns the association.
By the way, are you sure the mapping you are trying to achieve is not the one described in the Hibernate ORM documentation as Example 172. Bidirectional many-to-many with link entity
?
Upvotes: 1
Reputation: 94
Instead of using your method of returnBooke use this :
@Override
@Transactional
public void returnBok(Student student, Books book) {
try {
if (student != null && book != null) {
student.getBooks().removeIf(books -> {
return Objects.equals(book.getId(), books.getId());
});
entityManager.save(student);
}
} catch (Exception e) {
e.printStackTrace();
}
}
Because in above method you are removing whole student object entityManage.remove(student);
, thats why your student is delete and due to
cascade = CascadeType.ALL
in the mapping your book object is also deleted.
One more suggestion. Instead of populating Getter, Setter and Constructor.
Use Lombok plugin :
https://projectlombok.org/setup/maven
https://www.baeldung.com/lombok-ide
Upvotes: -1
Reputation: 180201
Your many-to-many relationship is not mapped correctly.
The documentation says this:
Every many-to-many association has two sides, the owning side and the non-owning, or inverse, side. The join table is specified on the owning side. If the association is bidirectional, either side may be designated as the owning side. If the relationship is bidirectional, the non-owning side must use the
mappedBy
element of theManyToMany
annotation to specify the relationship field or property of the owning side.
(emphasis added).
Your annotations map the relationship as if both sides owned it, which is incorrect.
Once you choose an owning side, you modify the relationship by updating the relationship field on the owning side and saving the modified owning entity. You have other answers showing how to do that. You probably want also to either make corresponding changes to the non-owning side or to refresh the non-owning entity to bring the relationship field on that side into sync.
Upvotes: 2
Reputation: 32517
It sohuld be
student.getBooks().remove(book);
entityManger.save(student)
Upvotes: 0