2013-04-02 67 views
0

我在Hibernate中使用EJB 3。 我有一個無狀態的會話Bean。該bean中有一個方法deleteItem。無法捕捉Hibernate OptimisticLockException

當客戶端調用deleteItem方法時,發生的刪除沒有任何問題。 但如果我試圖使用for循環調用deleteItem方法,並將該循環的限制設置爲5-10次,那麼有時候刪除失敗。但不總是。

刪除操作實際上從2個表中刪除數據。子表和父表。 每個刪除通過執行刷新操作來提交。

正如我已經提到,如果我一個一個執行刪除,那麼沒有問題發生,它只發生在我試圖同時運行它。我得到的例外是

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並再次執行刪除操作。 我檢查了Exception Class Name,它是EntityNotFound。但我看到在堆棧跟蹤中,我得到了OptimisticLockException以及StaleObjectStateException。

那麼,任何人都可以請指導我如何抓住這個OptimisticLockException?

+0

我認爲一個完整的堆棧跟蹤將有很大幫助,因爲重要的是,異常來自哪裏。 – wildhemp

+1

您應該相信錯誤消息所說的內容。如果它說外鍵約束被破壞,則意味着外鍵約束被破壞。增加樂觀的愛好不會改變任何事情。而且您不應該嘗試捕獲樂觀鎖定異常並繼續使用相同的會話和實體。它不會工作。閱讀http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/#transactions-demarcation-exceptions –

+0

@JBNizet - 我明白你的觀點。但問題是它並不總是發生。它只發生在併發發生時。是的,你是對的,在處理樂觀鎖異常之後,我應該在新的事務中運行它。 – ifti24

回答

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. 想想你想做什麼:是德leteFamily應該在一次交易中發生?是?然後以這種方式實施。刪除子項後不要嘗試進行部分提交。
  3. 讓別人爲您管理交易更方便。只需將方法標記爲@Transactional並讓JTA框架處理細節即可。
  4. 而DAO方法甚至不應該嘗試做交易。考慮一下:你可能想稍後實現一個使用多個DAO方法的服務。如果他們每個人都試圖在不同的交易中承諾自己,那麼服務電話就不能成爲交易。那很糟。所以如果你想重用你的DAO方法,把事務性的東西放在它們上面的一個單獨的層中。