【问题标题】:Failed to Catch Hibernate OptimisticLockException捕获 Hibernate OptimisticLockException 失败
【发布时间】:2013-03-24 00:15:05
【问题描述】:

我将 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 ?

【问题讨论】:

  • 我认为完整的堆栈跟踪会有很大帮助,因为重要的是,异常来自哪里。
  • 您应该相信错误消息所说的内容。如果它说外键约束被破坏,则意味着外键约束被破坏。添加乐观的爱不会改变任何事情。而且您不应该尝试捕获乐观锁定异常并继续使用相同的会话和实体。它行不通。阅读docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/…
  • @JBNizet - 我明白你的意思。但问题是它并不总是发生。它仅在并发发生时发生。是的,你是对的,在处理完乐观锁异常后,我应该在新事务中运行它。

标签: java hibernate jakarta-ee jpa ejb


【解决方案1】:

你不应该。对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() children:

@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 方法,请将事务性内容拉入它们之上的单独层。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-01-24
    • 2016-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多