java.lang.StackOverflowError when persisting an object jpa
Solution 1
Your Category
class has list of Products
and in the equals
method of the Category
class you are doing
if (this.product_fk != other.product_fk && (this.product_fk == null || !this.product_fk.equals(other.product_fk))) {
return false;
}
which invokes the equals
method on the Product
class, the equals
method on the Product
class which does
if (this.category_fk != other.category_fk && (this.category_fk == null || !this.category_fk.equals(other.category_fk))) {
return false;
}
which invokes the equals
method on the Category
once again and the whole process repeats causing an Stack overflow.
Solution:
- Either remove the bi-direction dependency.
- Fix the equals method.
Hope it helps.
Solution 2
I am suspecting circular dependency as the root cause of the issue. I am thinking you have mapped Product
either in Category
or SaleDetails
or both the objects. If so, it will call circular reference issue while serializing the Product
object, while will result into StackOverFlow
error.
I think you have two options:
- Remove
bi-dreictional
mapping if it can be avoided. - Please implement
readObject()
andwriteObject()
methods in yourProduct
,Category
andSaleDetails
classes and avoid reading/writing objects in circles.
EDIT:
private void writeObject(ObjectOutputStream oos) throws IOException {
// default serialization
oos.defaultWriteObject();
// write the object attributes
oos.writeInt(product_id);
oos.writeObject(name);
oos.writeObject(description);
oos.write(imageFile);
oos.writeFloat(price);
oos.writeObject(dateAdded);
oos.writeObject(category_fk);
oos.writeObject(saleDetails_fk);
}
private void readObject(ObjectInputStream ois)
throws ClassNotFoundException, IOException {
// default deserialization
ois.defaultReadObject();
//read the attributes
product_id = ois.readInt();
name = (String)ois.readObject();
description = (String)ois.readObject();
imageFile = ois.read();
price = ois.readFloat();
dateAdded = (Date)ois.readObject();
category_fk = (Category)ois.readObject();
saleDetails_fk = (SaleDetails)ois.readObject();
}
Hope this helps.
Solution 3
As @Sajan mentioned. You have a cyclic dependency in your equals(). You need to change the equals() method in your Category class not to refer to to 'Product' list and 'categoryId' . It should probably be as follows -
public class Category implements Serializable
{
...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
return false;
}
if ((this.description == null) ? (other.description != null) : !this.description.equals(other.description)) {
return false;
}
return true;
}
}
In the equals() method in Product class you may have to remove 'ProductId' and 'Price'.
The equals() and hashcode() methods are important because they determine object equality when you use objects in detached state and add them to a java.util.Set. The recommended implementation is to use 'properties that form a natural key identifier' in your equals() and hashcode() implementations.
Category - Let's say you had CategoryA with ProductA and ProductB. It is not possible that the same ProductA and ProductB will be tied to a different category called CategoryB. So, they should not be part of the equals() implementation.
Product - Whether to include 'Category' in Product.equals() depends on whether 'Category' is part of the natural key identifier for a Product. And the fact that you are using a List inside Category means that you are not too concerned about object equality. If you are concerned about equality(), I would recommend that you change it to a Set.
If you had the following scenario -
Category -
Electronics
Product1 -
name - Camera | price - $100 | category - Electronics
Product2 -
name - HandyCam | Price - $200 | Category - Electronics
If 'Electronics' category has Set of two products as shown above. If you had the following sample code -
session.startTransaction();
Category electronics = session.get(Category.class, 1234);
Set<Product> products = electronics.getProducts();
session.commit();
Product camera = product.get(0);
camera.setPrice("300");
products.add(camera);
When you change the price of camera and add it back in to the Set, you want to make sure that the set still contains only two elements and not add a third new element because you are modifying an existing product, not adding a new Product.
For the above scenario, you need to have 'Category' and 'name' in the equals() method of 'Product'.
![lv10](https://i.stack.imgur.com/RzAHj.jpg?s=256&g=1)
lv10
lv10 = { 'software_engineer': { 'title': 'Python Developer', 'languages': ['python', 'c++', 'c'], 'interests': ['flask', 'Rest API', 'AWS', 'Data Science'] } }
Updated on June 18, 2022Comments
-
lv10 about 2 years
I am building an application using JPA, JSF, EJB, Derby. At this point the application is still small. I have a form in the application to add new products. When adding data to the db it goes smoothly until I restart the application or the server. When I restart either the server or app I get
java.lang.StackOverflowError
, I still can query the db for the data represented by the product db, but creation product is not possible. I have only 5 entries in the db, as of now, but I am concerned about this happening so early.This is the Ejb (Getter, setter and constructors removed for simplicity):
@Stateless public class ProductEJB{ @PersistenceContext(unitName = "luavipuPU") private EntityManager em; public List<Product> findAllProducts() { TypedQuery<Product> query = em.createNamedQuery("findAllProducts", Product.class); return query.getResultList(); } public Product findProductById(int productId) { return em.find(Product.class, productId); } public Product createProduct(Product product) { product.setDateAdded(productCreationDate()); em.persist(product); return product; } public void updateProduct(Product product) { em.merge(product); } public void deleteProduct(Product product) { product = em.find(Product.class, product.getProduct_id()); em.remove(em.merge(product)); }
this is the ProductController (Getter, setter and constructors removed for simplicity):
@Named @RequestScoped public class ProductController { @EJB private ProductEJB productEjb; @EJB private CategoryEJB categoryEjb; private Product product = new Product(); private List<Product> productList = new ArrayList<Product>(); private Category category; private List<Category> categoryList = new ArrayList<Category>(); public String doCreateProduct() { product = productEjb.createProduct(product); productList = productEjb.findAllProducts(); return "listProduct?faces-redirect=true"; } public String doDeleteProduct() { productEjb.deleteProduct(product); return "deleteProduct?faces-redirect=true"; } public String cancelDeleteAction() { return "listProduct?faces-redirect=true"; } @PostConstruct public void init() { categoryList = categoryEjb.findAllCategory(); productList = productEjb.findAllProducts(); }
Category Entity (Getters, setters, hash() and constructors removed for simplicity):
@Entity @NamedQueries({ @NamedQuery(name= "findAllCategory", query="SELECT c FROM Category c") }) public class Category implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy= GenerationType.AUTO) private int category_id; private String name; private String description; @OneToMany(mappedBy = "category_fk") private List<Product> product_fk; // readObject() and writeObject() private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // default deserializer ois.defaultReadObject(); // read the attributes category_id = ois.readInt(); name = (String)ois.readObject(); description = (String)ois.readObject(); } private void writeObject(ObjectOutputStream oos) throws IOException, ClassNotFoundException { // default serializer oos.defaultWriteObject(); // write the attributes oos.writeInt(category_id); oos.writeObject(name); oos.writeObject(description); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Category other = (Category) obj; if (this.category_id != other.category_id) { return false; } if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { return false; } if ((this.description == null) ? (other.description != null) : !this.description.equals(other.description)) { return false; } if (this.product_fk != other.product_fk && (this.product_fk == null || !this.product_fk.equals(other.product_fk))) { return false; } return true; }
Product Entity (Getters, setters, hash() and constructors removed for simplicity):
@Entity @NamedQueries({ @NamedQuery(name="findAllProducts", query = "SELECT p from Product p") }) public class Product implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy= GenerationType.AUTO) private int product_id; private String name; private String description; protected byte[] imageFile; private Float price; @Temporal(TemporalType.TIMESTAMP) private Date dateAdded; @ManyToOne private Category category_fk; @ManyToOne private SaleDetails saleDetails_fk; // readObject() and writeObject() methods private void readObject (ObjectInputStream ois)throws IOException, ClassNotFoundException { // default deserialization ois.defaultReadObject(); // read the attributes product_id = ois.readInt(); name = (String)ois.readObject(); description = (String)ois.readObject(); for(int i=0; i<imageFile.length; i++ ) { imageFile[i]=ois.readByte(); } price = ois.readFloat(); dateAdded = (Date)ois.readObject(); category_fk = (Category)ois.readObject(); saleDetails_fk = (SaleDetails)ois.readObject(); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Product other = (Product) obj; if (this.product_id != other.product_id) { return false; } if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { return false; } if ((this.description == null) ? (other.description != null) : !this.description.equals(other.description)) { return false; } if (!Arrays.equals(this.imageFile, other.imageFile)) { return false; } if (this.price != other.price && (this.price == null || !this.price.equals(other.price))) { return false; } if (this.dateAdded != other.dateAdded && (this.dateAdded == null || !this.dateAdded.equals(other.dateAdded))) { return false; } if (this.category_fk != other.category_fk && (this.category_fk == null || !this.category_fk.equals(other.category_fk))) { return false; } if (this.saleDetails_fk != other.saleDetails_fk && (this.saleDetails_fk == null || !this.saleDetails_fk.equals(other.saleDetails_fk))) { return false; } return true; } private void writeObject(ObjectOutputStream oos) throws IOException, ClassNotFoundException { // default serialization oos.defaultWriteObject(); // write object attributes oos.writeInt(product_id); oos.writeObject(name); oos.writeObject(description); oos.write(imageFile); oos.writeFloat(price); oos.writeObject(dateAdded); oos.writeObject(category_fk); oos.writeObject(saleDetails_fk); }
This is the stacktrace:
javax.faces.el.EvaluationException: java.lang.StackOverflowError at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:102) at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:102) at javax.faces.component.UICommand.broadcast(UICommand.java:315) at javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:794) at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:1259) at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:81) at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101) at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118) at javax.faces.webapp.FacesServlet.service(FacesServlet.java:593) at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1550) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:281) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175) at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:655) at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:161) at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:331) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231) at com.sun.enterprise.v3.services.impl.ContainerMapper$AdapterCallable.call(ContainerMapper.java:317) at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:195) at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:860) at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:757) at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1056) at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:229) at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137) at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104) at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90) at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79) at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54) at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59) at com.sun.grizzly.ContextTask.run(ContextTask.java:71) at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532) at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513) at java.lang.Thread.run(Thread.java:722) Caused by: java.lang.StackOverflowError at java.util.Vector$Itr.<init>(Vector.java:1120) at java.util.Vector.iterator(Vector.java:1114) at java.util.AbstractList.hashCode(AbstractList.java:540) at java.util.Vector.hashCode(Vector.java:988) at org.eclipse.persistence.indirection.IndirectList.hashCode(IndirectList.java:460) at com.lv.Entity.Category.hashCode(Category.java:96) at com.lv.Entity.Product.hashCode(Product.java:148) at java.util.AbstractList.hashCode(AbstractList.java:541)