0

我将 EJB 3 与 Hibernate 一起使用。我有一个无状态会话 Bean。该 bean 中有一个方法 deleteItem。

当客户端调用 deleteItem 方法时,删除发生没有任何问题。但是,如果我尝试使用 for 循环调用 deleteItem 方法并将该循环的限制设置为 5-10 次,那么有时删除会失败。但不总是。

删除操作实际上是从 2 个表中删除数据。子表和父表。每个 Delete 都是通过执行刷新操作来提交的。

正如我已经提到的,如果我一个一个地执行删除,那么就不会发生问题,只有当我尝试同时运行它时才会发生。我得到的例外如下

Caused by: java.sql.BatchUpdateException: Cannot delete or update a parent row: a foreign key
constraint fails (`functionTest/MotherTable`, CONSTRAINT `FKBC6CB0E6D5545EFD` FOREIGN KEY
(`MotherTable_FieldId`) REFERENCES `ChildTable` (`childTableId`))

这里没有办法发生并发删除操作。并且项目删除与其他项目删除操作无关。因此,如果仍然发生并发,同时删除多个项目不会有问题。

所以,我做出了一个决定——“可能是客户端在多线程中访问同一个 Bean 实例”。在这种情况下,两个线程将相同的实体管理器状态保持在不同的状态。当另一个尚未完成子项的删除时,一个尝试刷新持久性上下文。此时发生了 BatchUpdateException。- 这是我的观察。我不是 100% 确定的。

所以为了克服这种情况,我选择了乐观锁定。我在母表中创建了版本列。现在我得到了 OptimisticLockException 。但我无法捕捉到异常。下面是我用来捕获 OptimisticLockException 的代码。

private boolean deleteItem(Item itemId) {

           Item item= getItem(itemId);
       removeChildTableData(item);
       mEm.remove(item);

    try
            {
       mEm.flush();
    }
    catch (OptimisticLockException  e) 
            {
         try {
            Thread.sleep(1000);
             } 
                     catch (InterruptedException e1) {
            e1.printStackTrace();
         }
       deleteItem(itemId);

        }
    catch(Exception ex)
                 {

        if (ex.getCause() instanceof OptimisticLockException) 
                     {
                       try {
                            Thread.sleep(1000);
                           } catch (InterruptedException x) {
                           }
                     deleteItem(itemId);

                     }


      }

    return true;
   }

所以我的目标是捕获 OptimisticLockException 并再次重新执行删除操作。我检查了异常类名称,它是 EntityNotFound。但我看到在堆栈跟踪中我得到了 OptimisticLockException 和 StaleObjectStateException。

那么,有人可以指导我如何捕捉这个 OptimisticLockException 吗?

4

1 回答 1

0

你不应该。对JB所说的也+1。这个例外试图告诉你一些事情。您正在尝试删除外键关系的父行,而孩子仍在引用它。什么是父母和孩子?好:

a foreign key constraint fails (`functionTest/MotherTable`, CONSTRAINT `FKBC6CB0E6D5545EFD` FOREIGN KEY (`MotherTable_FieldId`) REFERENCES `ChildTable` (`childTableId`))

所以 MotherTable.MotherTableFieldId 引用 ChildTable.childTableId。你试图删除一个孩子,而它的母亲仍然指向它。那是行不通的。

不过,我很好奇你为什么会有这种关系。您的模型看起来像这样:

@Entity
@Table(name="MotherTable")
class Mother {
  @Id
  Long id;
  @ManyToOne
  @JoinColumn(name="MotherTable_FieldId")
  Child child;
}

@Entity
@Table(name="ChildTable"
class Child {
  @Id
  @Column(name="childTableId")
  Long id; 
  @OneToMany(mappedBy="child")
  Set<Mother> mothers;
}

这很奇怪,因为现在你的孩子可以有很多妈妈。也许你想要这个:

@Entity
class Mother {
  @Id
  Long id;
  @OneToMany(mappedBy="mother")
  Set<Child> children;
}

@Entity
class Child {
  @Id
  Long id; 
  @ManyToOne
  @JoinColumn(name="mother_id")
  Mother mother;
}

在这种情况下,您的 DAO 方法将如下所示:

@Transactional
public void deleteFamily(Mother mother) {
  for (Child c: mother.getChildren()) {
    em.remove(c);
  }
  em.remove(mother);
}

您还可以使用级联:

@Entity
class Mother {
  @Id
  Long id;
  @OneToMany(mappedBy="mother", cascading=CascadeType.ALL)
  Set<Child> children;
}

这将 DAO 方法简化为:

@Transactional
public void deleteFamily(Mother mother) {
  em.remove(mother);
}

乃至:

@Entity
class Mother {
  @Id
  Long id;
  @OneToMany(mappedBy="mother", cascading=CascadeType.ALL, orphanRemoval=true)
  Set<Child> children;
}

现在你不必em.remove()孩子:

@Transactional
public void deleteChild(Child child) {
  Mother m = child.getMother();
  m.getChildren().remove(child);
}

此外,您不应该尝试使用 提交事务em.flush(),这在几个方面是错误的:

  1. 事务提交与em.getTransaction().commit()
  2. 想想你想要做什么:deleteFamily 应该在一个事务中发生吗?是的?然后以这种方式实现它。删除子项后不要尝试进行部分提交。
  3. 让其他人为您管理交易会更方便。只需将方法标记为 @Transactional 并让您的 JTA 框架处理细节。
  4. 无论如何,DAO 方法甚至都不应该尝试进行交易。想一想:您可能希望稍后实现一个使用多种 DAO 方法的服务。如果他们每个人都试图在单独的事务中提交自己,则 toto 中的服务调用不能是事务。那很糟。因此,如果您想重用您的 DAO 方法,请将事务性内容拉到它们上方的单独层中。
于 2013-04-02T17:23:04.527 回答