在使用JPA持久化对象时出现java.lang.StackOverflowError错误

8
我正在使用JPA、JSF、EJB和Derby构建一个应用程序。目前,这个应用程序还很小。我在应用程序中有一个表单来添加新产品。当向数据库添加数据时,一切顺利,直到我重新启动应用程序或服务器。重新启动服务器或应用程序时,我会遇到java.lang.StackOverflowError错误。我仍然可以查询代表产品数据库的数据,但无法创建产品。目前,我只有5个条目在数据库中,但我担心这种情况会在这么早的阶段发生。
这是Ejb:(为简单起见,获取器、设置器和构造器已被删除):
@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));
    }

这是ProductController(为简化起见,省略了Getter、Setter和构造函数):
    @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();        
    }

分类实体(为简化起见,省略了getter、setter、hash()和构造函数):

@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;
    }

产品实体(为简化起见,省略了getter、setter、hash()和构造函数):

@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);

    }

这是堆栈跟踪信息:
    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)

你能发一下Category类吗? - IllegalArgumentException
@breezee 谢谢。我刚刚用 Category 类更新了帖子。提前致谢。 - lv10
看起来你已经得到了答案。 - IllegalArgumentException
1
你能在Product和Category类中发布你的equals()实现吗?在Product中的equals()方法是否可能调用Category中的equals()方法,反之亦然? - Sashi
@Sashi 我已经在上面的代码中更新了Product和Category的equals()方法。谢谢 - lv10
6个回答

15

你的Category类有一个Products列表,在Category类的equals方法中,你正在执行

if (this.product_fk != other.product_fk && (this.product_fk == null || !this.product_fk.equals(other.product_fk))) {
    return false;
}

调用了Product类上的equals方法,Product类上的equals方法的作用是

if (this.category_fk != other.category_fk && (this.category_fk == null || !this.category_fk.equals(other.category_fk))) {
    return false;
}

调用Category上的equals方法,再次触发整个过程,导致栈溢出。

解决方案:

  1. 删除双向依赖关系。
  2. 修复equals方法。

希望这可以帮到你。


谢谢Sajan,我已经尝试修改两个类的equals()方法,并通过删除其中评估!this.product_fk.equals(other.product_fk)!this.category_fk.equals(other.category_fk)的部分来消除了stackOverflowError。然而,我担心不实现这个条件会产生逻辑上的影响。你会推荐我使用的修复方法吗?你能告诉我你对这个问题的看法吗?提前感谢。 - lv10
我有一个问题。尝试完成产品的更新操作时,我遇到了相同的“StackOverflow”错误。然而,这次它是关于“哈希”的,类似于这样: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) 我想问你,这个问题是否与我们之前讨论的问题相同。提前感谢。 - lv10
这是正确的。在等式和哈希码的计算中,您不应该使用您的关系。您不应该使用任何可能在哈希码中发生变化的内容。 - Chris

1
我怀疑循环依赖是问题的根本原因。我认为您已经在CategorySaleDetails对象中映射了Product,或者两个对象都映射了它。如果是这样,在序列化Product对象时会调用循环引用问题,导致StackOverFlow错误。
我认为您有两个选择:
  1. 如果可以避免,请删除双向映射。
  2. 请在您的ProductCategorySaleDetails类中实现readObject()writeObject()方法,并避免循环读取/写入对象。

编辑:

 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();
    } 

希望这能有所帮助。

1
@lv10 没有问题。只需实现您自定义的readObject/writeObject方法即可。如果需要更多详细信息,请告诉我。 - Yogendra Singh
我并没有真正使用过 readObject() 和 writeObject(),你有一个我可以用来参考 JPA 的例子吗?……再次感谢,我非常感激。 - lv10
1
@lv10 添加了Product类的示例代码。在CustomerSaleDetails类中添加类似的方法。请不要读/写CustomerSaleDetails类的Product属性。 - Yogendra Singh
1
@lv10:为了确保我们正在处理问题的正确原因,您能否将您的关系设置为单向(暂时注释掉一个方向的映射)然后再尝试一下?如果它可以工作,我们就会知道这是循环依赖关系,然后我会与您合作解决这个问题。我只是想避免在最终没有价值的方向上努力。 - Yogendra Singh
1
@lv10:这证明了我在识别问题原因上是正确的。如果你真的不需要双向映射,最好(删除一个方向的映射),否则我们就继续那个序列化任务吧。请分享你实现的readObject/writeObject代码。 - Yogendra Singh
显示剩余9条评论

1
正如 @Sajan 所提到的,您在 equals() 方法中存在循环依赖。您需要更改 Category 类中的 equals() 方法,不要引用 'Product' 列表和 'categoryId'。它应该像下面这样 -
    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;
    }
}

在Product类的equals()方法中,您可能需要删除“ProductId”和“Price”。
equals()和hashcode()方法非常重要,因为它们确定对象的相等性,当您将对象用于分离状态并将它们添加到java.util.Set时。建议的实现是在equals()和hashcode()实现中使用“形成自然键标识符的属性”。
类别 - 假设您有CategoryA和ProductA以及ProductB。不可能将相同的ProductA和ProductB绑定到称为CategoryB的不同类别中。因此,它们不应该成为equals()实现的一部分。
产品 - 是否在Product.equals()中包含“Category”取决于“Category”是否是Product的自然键标识符的一部分。而且,您在Category内部使用List的事实意味着您对对象相等性不太关心。如果您关心equality(),我建议您将其更改为Set。
如果您有以下情况 -
分类 - 电子产品 产品1 - 名称 - 相机 | 价格 - $100 | 类别 - 电子产品 产品2 - 名称 - 手机摄像机 | 价格 - $200 | 类别 - 电子产品
如果“电子产品”类别有如上所示的两个产品集。 如果您有以下示例代码 -
        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);

当您更改相机的价格并将其添加回套装中时,您希望确保该套装仍然仅包含两个元素,并且不会添加第三个新元素,因为您正在修改现有产品,而不是添加新产品。

对于上述情况,您需要在“产品”的equals()方法中拥有“类别”和“名称”。


0

看起来问题出在Category类中 - equals方法会调用其他的equals方法,从而导致无限循环


0
List.equalsVector.equals 的调用中,我会认为在你的实体中有一个包含向量 Y 的列表 X。对该列表执行 equals 调用将遍历该列表,该列表将遍历向量,向量将遍历列表...以此类推。

0

你的代码中存在循环结构。类生成的方法,包括equals、hashCode、toString无法处理这种循环行为。这些方法没有处理这种情况的方式。

请从这些方法中排除可能导致这些情况发生的字段。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接