java.lang.StackOverflowError when persisting an object jpa

17,080

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:

  1. Either remove the bi-direction dependency.
  2. 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:

  1. Remove bi-dreictional mapping if it can be avoided.
  2. Please implement readObject() and writeObject() methods in your Product, Category and SaleDetails 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'.

Share:
17,080
lv10
Author by

lv10

lv10 = { 'software_engineer': { 'title': 'Python Developer', 'languages': ['python', 'c++', 'c'], 'interests': ['flask', 'Rest API', 'AWS', 'Data Science'] } }

Updated on June 18, 2022

Comments

  • lv10
    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)