java.lang.StackOverflowError 时持久化对象 jpa



我正在使用JPA,JSF,EJB,Derby构建一个应用程序。此时,应用程序仍然很小。我在应用程序中有一个表格来添加新产品。将数据添加到数据库时,它会顺利进行,直到我重新启动应用程序或服务器。当我重新启动服务器或应用程序时,我得到java.lang.StackOverflowError,我仍然可以在数据库中查询产品db表示的数据,但无法创建产品。截至目前,我在数据库中只有 5 个条目,但我担心这种情况发生得这么早。

这是 Ejb(为简单起见,删除了 getter、setter 和构造函数):

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

这是产品控制器(为简单起见,删除了 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();        
}

类别实体(为简单起见,删除了 Getters、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;
}

产品实体(为简单起见,删除了 Getters、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类具有Products列表,并且equals您正在执行的Category类的方法

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方法,整个过程重复导致 Stack 溢出。

溶液:

  1. 删除双向依赖项。
  2. 修复等于方法。

希望对您有所帮助。

我怀疑循环依赖是问题的根本原因。我认为您已经在CategorySaleDetails或两个对象中映射了Product。如果是这样,它将在序列化Product对象时调用循环引用问题,同时将导致StackOverFlow错误。

我认为您有两种选择:

  1. 如果可以避免,请删除bi-dreictional映射。
  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();
} 

希望这有帮助。

正如@Sajan提到的。你在 equals() 中有一个循环依赖关系。 您需要更改类别类中的 equals() 方法,以不引用"产品"列表和"类别 ID"。大概应该是这样的——

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

在产品类的 equals() 方法中,您可能需要删除"ProductId"和"Price"。

equals() 和 hashcode() 方法很重要,因为当您使用处于分离状态的对象并将其添加到 java.util.Set 时,它们决定了对象的相等性。 推荐的实现是在 equals() 和 hashcode() 实现中使用"形成自然键标识符的属性"。

类别- 假设您有产品 A 和产品 B 的类别 A。同一产品 A 和产品 B 不可能绑定到称为类别 B 的不同类别。因此,它们不应该是 equals() 实现的一部分。

产品- 是否在 Product.equals() 中包含"类别"取决于"类别"是否是产品的自然键标识符的一部分。事实上,你在类别中使用列表意味着你不太关心对象相等性。如果你关心 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() 方法中包含"类别"和"名称"。

似乎问题出在类别类中 - equals 做了一些事情,而这些事情又调用 equals,创建了一个无限循环

List.equalsVector.equals调用中,我认为有一个列表X包含一个向量Y包含列表X在您的实体中的某个地方。对该列表执行相等调用将遍历该列表,该列表将遍历向量,向量将遍历列表...等等。

代码中有一个循环结构。类生成的方法(包括equals、hashCode、toString)无法处理这种循环行为。 这些方法没有处理此类方案的方法。

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

最新更新