This problem is given when we try to fetch at the same time more than one object with relation One-to-many, Many-to-one or Many-to-many.
There are several way for solving this problem, one of them is not working for me but the one that I'm going to explain works perfectly.
Let us assume that we have three entity:
BOOK
AUTHOR
ORDER
A book has a relation Many-To-Many with Author
and Order has relation Many-To-Many with Book
Let us take as example the entity book and author as the one used in the previous post named: EJB:many to many relation
and let us add the entity Order:
-----------------------
public class Orders implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Basic(optional = false)
@Column(name = "Order_Id")
private Integer Order_Id;
@Basic(optional = false)
@Column(name = "Refers_To")
private String refersTo;
@Basic(optional = false)
@Column(name = "Price_Amount")
private double priceAmount;
@Basic(optional = false)
@Column(name = "OrderDate")
private String orderDate;
@Basic(optional = false)
@Column(name = "Deleted")
private boolean deleted;
@JoinTable(name = "ORDERS_BOOK", joinColumns = {
@JoinColumn(name = "Order_Id", referencedColumnName = "Order_Id")}, inverseJoinColumns = {
@JoinColumn(name = "Book_Id", referencedColumnName = "Book_Id")})
@ManyToMany(cascade = {CascadeType.MERGE}, fetch=FetchType.EAGER)
private List<Book> bookList;
public Orders() {
}
public Orders(Integer Order_Id) {
this.Order_Id = Order_Id;
}
public Orders(Integer Order_Id, String refersTo, double priceAmount, String orderDate, boolean deleted) {
this.Order_Id = Order_Id;
this.refersTo = refersTo;
this.priceAmount = priceAmount;
this.orderDate = orderDate;
this.deleted = deleted;
}
public Integer getOrder_Id() {
return Order_Id;
}
public void setOrder_Id(Integer Order_Id) {
this.Order_Id = Order_Id;
}
public String getRefersTo() {
return refersTo;
}
public void setRefersTo(String refersTo) {
this.refersTo = refersTo;
}
public double getPriceAmount() {
return priceAmount;
}
public void setPriceAmount(double priceAmount) {
this.priceAmount = priceAmount;
}
public String getOrderDate() {
return orderDate;
}
public void setOrderDate(String orderDate) {
this.orderDate = orderDate;
}
public boolean getDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public List<Book> getBookList() {
return bookSet;
}
public void setBookList(List<Book> bookList) {
this.bookSet = bookSet;
}
@Override
public int hashCode() {
int hash = 0;
hash += (Order_Id != null ? Order_Id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Orders)) {
return false;
}
Orders other = (Orders) object;
if ((this.Order_Id == null && other.Order_Id != null) || (this.Order_Id != null && !this.Order_Id.equals(other.Order_Id))) {
return false;
}
return true;
}
@Override
public String toString() {
return "entity.Orders[Order_Id=" + Order_Id + "]";
}
}
In this case when compiling, jboss, will gave us the exception
"javax.persistence.PersistenceException: org.ejb3unit.hibernate.HibernateException: cannot simultaneously fetch multiple bags"
WORKAROUND
A workaround for this is to use Set instead of List in entity Order for example (or Book...or any other entity in which we are using a list for a many to many, one to many, many to one relation). Do you really need a List?? Or is it enough to have a Set??
(I know list is more comfortable....but we can add in the getter and setter mode some code that transform our ugly set in a wonderful list! ;) )
Let us turn back to the problem! This issue arised by Hibernate is that it is not able to load automatically more than one collection at a time, using a set instead we are able to load multiple collection in a EAGER fetchType manner.
The previous bookList will become a bookSet. I'll put how the new bookList looks like:
@JoinTable(name = "ORDERS_BOOK", joinColumns = {
@JoinColumn(name = "Order_Id", referencedColumnName = "Order_Id")}, inverseJoinColumns = {
@JoinColumn(name = "Book_Id", referencedColumnName = "Book_Id")})
@ManyToMany(cascade = {CascadeType.MERGE}, fetch=FetchType.EAGER)
private Set<Book> bookSet;
With its proper getter and setter. In this way we have fixed this problem.
Note that also using a LAZY fetchType we will solve this problem, but if we need
the compete object to be loaded (as it was the case in this explanation), LAZY fetch type is completely useless and wrong.
No comments:
Post a Comment