2012-01-04 64 views
7

我們有一個運行在JBoss 5.1上的Java應用程序,在某些情況下,我們需要防止某個基礎方法拋出JDBCException時事務被關閉。防止在JBoss + Hibernate中進行事務回滾

我們有一個看起來像下面的一個

@PersistenceContext(unitName = "bar") 
public EntityManager em; 

public Object foo() { 
    try { 
    insert(stuff); 
    return stuff; 
    } (catch PersistenceException p) { 
    Object t = load(id); 
    if (t != null) { 
     find(t); 
     return t; 
    } 
    } 
} 

如果insert因爲PersistenceException的失敗(它包裝引起的約束違反JDBCException),我們希望繼續與中load執行EJB方法同樣的交易。

我們現在無法做到這一點,因爲交易是由容器關閉的。下面是我們在日誌中看到:

org.hibernate.exception.GenericJDBCException: Cannot open connection 
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Cannot open connection 
    at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:614) 

    ... 

Caused by: javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000101:85fe:4f04679d:182 status: ActionStatus.ABORT_ONLY > 

EJB類標有以下注釋

@Stateless 
@TransactionManagement(TransactionManagementType.CONTAINER) 
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 

,以防止有交易的回滾在短短這個特定的情況下,任何適當的方式?

回答

5

你真的不應該這樣做。正如另一個答案中提到的,並且引用Hibernate Docs,Hibernate拋出的異常應該被視爲可恢復。這可能會導致您難以發現/調試問題,特別是使用hibernate自動髒檢查。

解決此問題的一種簡單方法是在插入對象之前檢查這些約束。使用查詢來檢查是否違反了數據庫約束。

public Object foo() { 
    if (!objectExists()) { 
     insertStuff(); 
     return stuff(); 
    } 
    // Code for loading object... 
} 

我知道這似乎有點痛苦,但是那是你肯定知道這是約束侵犯(你不能從Hibernate的異常信息)的唯一途徑。我相信這是最乾淨的解決方案(至少是最安全的)。


如果你仍然想從異常中恢復,你必須對代碼進行一些修改。

如上所述,您可以手動管理交易,但我不建議這樣做。 JTA API非常麻煩。此外,如果使用Bean Managed Transaction(BMT),則必須手動爲EJB中的每個方法創建事務,這是全部或全部。

另一方面,您可以重構您的方法,以便容器爲查詢使用不同的事務。事情是這樣的:

@Stateless 
public class Foo { 
    ... 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public Object foo() { 
     try { 
      entityManager.insert(stuff); 
      return stuff; 
     } catch (PersistenceException e) { 
      if (e.getCause() instanceof ConstraintViolationException) { 
       // At this point the transaction has been rolled-backed. 
       // Return null or use some other way to indicate a constrain 
       // violation 
       return null; 
      } 
      throw e; 
     } 
    } 

    // Method extracted from foo() for loading the object. 
    public Object load() { 
     ... 
    } 
} 

// On another EJB 
@EJB 
private Foo fooBean; 

public Object doSomething() { 
    Object foo = fooBean.insert(); 
    if (foo == null) { 
     return fooBean.load(); 
    } 

    return foo; 
} 

當你調用foo(),當前事務(T1)將被暫停,容器將創建一個新的(T2)。發生錯誤時,T2將被回捲,T1將被恢復。當調用load()時,它將使用T1(它仍然處於活動狀態)。

希望這會有所幫助!

2

我不認爲這是可能的。

可能取決於您的JPA提供程序,但是,例如,Hibernate明確指出,任何異常都會導致會話處於不一致狀態,因此不應被視爲可恢復(13.2.3. Exception handling)。

我想你可以做的最好的事情是禁用這種方法的自動事務管理,並手動創建異常後的新事務(使用UserTransaction,據我記憶)。