java.lang.StackOverflowError when persisting an object jpa
Asked Answered
A

6

8

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)
Attenuant answered 4/11, 2012 at 23:39 Comment(5)
Can you post the Category class?Endamage
@breezee Thanks. I just updated the post with the Category class. Thanks in advance.Attenuant
seems like you got your answer..Endamage
Could you post your equals() implementation in Product and Category classes? Is it possible that the equals() method in Product is calling the equals() method in Category and viceversa.Bernini
@Bernini I have updated the code above with the equals() methods for both Product and Category. ThanksAttenuant
D
15

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.

Distrust answered 5/11, 2012 at 23:25 Comment(3)
Thanks Sajan, I have tried modifying the equals() on both classes and by removing the porting where it evaluates !this.product_fk.equals(other.product_fk) and !this.category_fk.equals(other.category_fk) the stackOverflowError goes away. However, I am concerned about the logical implication not implementing this condition. Would you recommend the fix I used? could you let me know your reasoning on this matter? thanks in advance.Attenuant
I have a question. Trying to complete an update operation for a product, I'm getting the same StackOverflow error. However, this time it is referring to the hash, something like this: 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) I wanted to ask you if the problem is the same problem that we were discussing before. Thanks in advance.Attenuant
It is. You should not be using your relationships in equals and hashcode calculations. You shouldn't use anything that might change in a hashcode at all.Morelli
D
1

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.

Discography answered 4/11, 2012 at 23:46 Comment(14)
thanks for the response. As you mentioned I have mapped the Product in both Category and SaleDetails entities. Should I not serialize it?Attenuant
@Attenuant There is no issue. Just implement you custom readObject/writeObject methods. If you need more details about it, please let me know.Discography
I have not really worked much with readObject() and writeObject() do you have an example I can refer to for JPA? ... Thanks again I really appreciate it.Attenuant
@Attenuant Added sample code for Product class. Add similar methods in Customer and SaleDetails classes. Please don't read/write Product attribute of Customer and SaleDetails classes.Discography
Singh thanks for the example. I was able to create the methods in the other classes. As indicated I didn't do it for Product attribute in either of the read/writeObject() for the other methods. However, after running different tests, I found that the java.lang.StackOverflowError comes up again if I tried to save or save a product with any of the previous already saved; but if I create a new Category then I can save new products only to that new category. If restart the app it all starts again. I have to create new categories everytime I want to save new products. Thanks in advance.Attenuant
@lv10: Just to make sure that we are working on the right cause of issue, can you please make your relationship uni-directional(temporarily comment one direction mapping) and then try? If it works, we would know its the circular dependency for sure and then I will work with you to resolve the issue. I am just trying to avoid putting the effort in the direction of no value in the end.Discography
Yogendra, I have converted the relationship btwn Product and Category Uni-directional and after running a couple tests on the application now the 'stackOverflowError' is not showing anymore. So it seems that it is circular dependency. I did some research and found that one way is to remove in the product_fk from the 'Category' entity constructor. Would that be a good solution or is there a way of dealing with it that you recommend? Once again for your willingness to help.Attenuant
@lv10: So this proves, I was correct in identifying the cause of the issue. If you really don't need bi-directional mapping, its best (remove one direction mapping) otherwise let's resume that serialization task then. Please share your readObject/writeObject code you implemented.Discography
Thanks Yogendra. Fortunately/unfortunately I need them to be bi-directional. I have updated the code above reflecting the changes for the read/writeObject() methods.Attenuant
let us continue this discussion in chatDiscography
I was away from computer, would it be possible for you to chat tomorrow? at around 11 AM EST?Attenuant
@Attenuant YES, Lets chat tomorrow. Long back I had this issue so I know at least one solution which works but we will try our newer approach first.Discography
Thanks see you tmrw at 11 AM EST.Attenuant
@lv10: Since I didn't see you and couldn't chat around the newer approach, here is brief summary about the old one. Create POJO object in child objects. Copy the ENITY object values in the POJO object and return them from the service using copy constructor. While copying, don't copy the circular object. I nthe revers service, convert the POJO back to your entity objects and restore the circular reference. This is good idea from another side that your entity objects are not exposed to client side directly.Discography
B
1

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'.

Bernini answered 6/11, 2012 at 14:51 Comment(0)
C
0

It seems that the problem is in the Category class - the equals does something which in turn calls equals, creating an endless loop

Chericheria answered 4/11, 2012 at 23:45 Comment(0)
S
0

From the List.equals and Vector.equals calls, I would think there's a list X containing a vector Y containing list X somewhere in your entity. Performing an equals call on that list will iterate through that list, which will iterate through the vector, which will iterate through the list... and so on.

Silverweed answered 4/11, 2012 at 23:50 Comment(0)
P
0

There is a cyclic structure in your code. Class generated methods including equals, hashCode, toString cannot handle this cyclic behavior. Those methods do not have a way of handling such scenarios.

Please exclude the fields from those methods that could cause these situations.

Petterson answered 1/6, 2021 at 7:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.