2012-08-02 38 views
19

我正在努力解決管理非平凡數據模型的EJB3類的問題。當我的容器管理的事務方法提交時,我引發了約束驗證異常。我想阻止它們被封裝在EJBException中,而是拋出一個呼叫者可以處理的理智的應用程序異常。如何捕獲幷包裝由JTA在容器託管的tx EJB提交時拋出的異常?

要將它包裝在合適的應用程序例外中,我必須能夠抓住它。大多數情況下,一個簡單的try/catch可以完成這項工作,因爲驗證異常是從我做的一個EntityManager調用中拋出的。

不幸的是,一些約束只在提交時檢查。例如,在映射集合上違反@Size(min=1)時,只有在容器管理的事務提交後纔會捕獲它,一旦它在我的事務性方法結束時離開我的控制權。我無法捕捉到驗證失敗時拋出的異常並將其包裝,因此容器將其包裝在javax.transaction.RollbackException中,並將封裝在EJBException中。來電者必須抓住所有EJBException s並在原因鏈中進行潛水以試圖找出它是否是驗證問題,這是真的不好。

我和容器管理事務的工作,所以我的EJB是這樣的:

@Stateless 
@TransactionManagement(TransactionManagementType.CONTAINER 
class TheEJB { 

    @Inject private EntityManager em; 

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public methodOfInterest() throws AppValidationException { 
     try { 
      // For demonstration's sake create a situation that'll cause validation to 
      // fail at commit-time here, like 
      someEntity.getCollectionWithMinSize1().removeAll(); 
      em.merge(someEntity); 
     } catch (ValidationException ex) { 
      // Won't catch violations of @Size on collections or other 
      // commit-time only validation exceptions 
      throw new AppValidationException(ex); 
     } 
    } 

} 

...其中AppValidationException是經過檢查的異常或註釋的一個未經檢查的異常@ApplicationException所以它不會被包裹EJB3。

有時我可以通過EntityManager.flush()觸發早期約束違規,並且可以捕獲該約束,但並非總是如此。即使那樣,我也會真的喜歡能夠在提交時捕獲由延遲約束檢查引發的數據庫級約束違規,並且那些只有有史以來在JTA提交時出現。

幫助?


已經嘗試過:

Bean管理事務將允許我來觸發內的代碼,我控制承諾解決我的問題。不幸的是,它們不是一種選擇,因爲bean管理的交易不提供任何等效的TransactionAttributeType.REQUIRES_NEW--無法使用BMT暫停交易。 JTA的一個惱人的疏忽。

參見:

...但看到答案的注意事項和細節。

javax.validation.ValidationException是JDK異常;我無法修改它以添加@ApplicationException註釋以防止包裝。我不能繼承它來添加註釋;它是由EclpiseLink引發的,而不是我的代碼。我不知道標記@ApplicationException會阻止Arjuna(AS7的JTA impl)將它封裝在RollbackException中。

我試圖用EJB3攔截這樣的:

@AroundInvoke 
protected Object exceptionFilter(InvocationContext ctx) throws Exception { 
    try { 
     return ctx.proceed(); 
    } catch (ValidationException ex) { 
     throw new SomeAppException(ex); 
    } 
} 

...但現在看來,攔截火 JTA(這是明智的,通常需要),所以例外,我想趕還沒有被拋出。

我想我想要的是能夠定義一個例外過濾器,應用 JTA做它的事情。有任何想法嗎?


我正在使用JBoss AS 7.1.1.Final和EclipseLink 2.4.0。根據these instructions,EclipseLink作爲JBoss模塊安裝,但這對於手頭的問題無關緊要。


UPDATE:在這個問題上更多的思考之後,我已經意識到,除了JSR330驗證異常,我真的還需要能夠從DB和deadlock or serialization failure rollbacks with SQLSTATE 40P01 and 40001 respectively陷阱SQLIntegrityConstraintViolationException。這就是爲什麼只是試圖確保提交永遠不會拋出的方法將無法正常工作。檢查的應用程序異常不能通過JTA提交拋出,因爲JTA接口自然不會聲明它們,但未經檢查@ApplicationException帶註釋的異常應該可以。

看來,我可以在任何地方有用地捕獲應用程序異常,但我也可以 - 儘管不那麼謹慎 - 捕獲EJBException並針對JTA異常和底層驗證或JDBC異常進行深入研究,然後根據該異常做出決策。如果沒有JTA中的例外過濾器功能,我可能不得不這樣做。

+0

在我嘗試一個答案之前的澄清:'ValidationException'是第三方異常,你說?不是來自'javax.validation。*'包? – 2012-08-02 13:49:41

+0

對不起,措辭不妙。 「我沒有在我的代碼中定義一個例外,我無法控制,也無法修改」。 'javax.validation.ValidationException'。 – 2012-08-02 23:24:30

+0

還有最後一個問題:主動驗證(http://docs.oracle.com/javaee/6/api/javax/validation/Validator.html)是不可能的?(我認爲是這樣; EclipseLink使用代理的方式有些問題,或者阻止它在JPA提供程序提供的集合類中工作。) – 2012-08-03 00:31:23

回答

4

我還沒有試過這個。但我猜這應該可行。

@Stateless 
@TransactionManagement(TransactionManagementType.CONTAINER) 
class TheEJB { 

    @Inject 
    private TheEJB self; 

    @Inject private EntityManager em; 

    public methodOfInterest() throws AppValidationException { 
     try { 
      self.methodOfInterestImpl(); 
     } catch (ValidationException ex) { 
      throw new AppValidationException(ex); 
     } 
    } 

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public methodOfInterestImpl() throws AppValidationException { 
     someEntity.getCollectionWithMinSize1().removeAll(); 
     em.merge(someEntity); 
    }  
} 

容器有望啓動新的事務,並承諾內的methodOfInterest,因此,你應該能夠趕上在包裝方法例外。

PS:答案是基於@LairdNelson提供優雅的理念更新中...

+1

它會很好,如果它確實如此,但不幸的是AFAIK在EJB調用它自己的方法時不會通過攔截,JTA等;這只是一個直接的電話。 'TransactionAttributeType.REQUIRES_NEW'沒有效果。我想過一種將REQUIRES_NEW方法拆分成單獨的輔助EJB的類似方法,但這很醜陋,(b)對於處理不需要立即執行tx commit的REQUIRES方法沒有幫助。 – 2012-08-03 01:27:04

+0

然後用另一個EJB包裝一個EJB呢。這樣它應該攔截電話。 – 2012-08-03 01:31:41

+0

哦,對不起,我錯過了您評論的最後部分。但是,您應該通過顯式刷新來處理驗證異常,或者在調用EJB的地方處理。另一方面,我並沒有發現這個醜陋的個人原因,一個是持久層很像一個DAO,另外一個是業務接口。 – 2012-08-03 01:40:08

6

有一點需要注意什麼,我在原來的問題有關REQUIRES_NEW和BMT說。

請參閱EJB 3.1規範,部分13.6.1Bean管理事務劃分,在集裝箱責任。它讀取:

容器必須管理的客戶端調用一個企業Bean 實例與bean管理事務劃分如下。當 客戶端通過其中一個企業bean的客戶端視圖調用業務方法時,容器將掛起與客戶端請求相關聯的任何可能爲 的事務。如果與實例關聯交易 (如果有狀態會話 bean實例在以前的一些業務 方法啓動事務會發生這種情況),集裝箱聯營公司與本 交易的方法執行。如果攔截器方法與bean實例相關聯,則在攔截器方法調用 之前採取這些操作。

(斜體我的)。這很重要,因爲這意味着BMT EJB不會繼承具有關聯容器託管tx的調用方的JTA tx。任何當前tx是暫停,因此如果BMT EJB創建一個tx它是一個新的事務,並且當它提交時它只提交它的事務。

這意味着你可以使用開始,就好像它是有效REQUIRES_NEW提交事務的BMT EJB方法做這樣的事情:

@Stateless 
@TransactionManagement(TransactionManagementType.BEAN) 
class TheEJB { 

    @Inject private EntityManager em; 

    @Resource private UserTransaction tx; 

    // Note: Any current container managed tx gets suspended at the entry 
    // point to this method; it's effectively `REQUIRES_NEW`. 
    // 
    public methodOfInterest() throws AppValidationException, SomeOtherAppException { 
     try { 
      tx.begin(); 
      // For demonstration's sake create a situation that'll cause validation to 
      // fail at commit-time here, like 
      someEntity.getCollectionWithMinSize1().removeAll(); 
      em.merge(someEntity); 
      tx.commit(); 
     } catch (ValidationException ex) { 
      throw new AppValidationException(ex); 
     } catch (PersistenceException ex) { 
      // Go grubbing in the exception for useful nested exceptions 
      if (isConstraintViolation(ex)) { 
       throw new AppValidationException(ex); 
      } else { 
       throw new SomeOtherAppException(ex); 
      } 
     } 
    } 

} 

這讓我控制下的承諾。在我不需要跨越多個不同EJB的多個調用的事務的情況下,這允許我在代碼中處理所有錯誤,包括提交時的錯誤。

Java EE 6 tutorial page on bean managed transactions沒有提到這個,也沒有提到如何調用BMT。

關於BMT不能在我鏈接的博客中模擬REQUIRES_NEW的討論是有效的,只是不清楚。如果你有一個bean管理的事務EJB,你不能暫停你開始的一個事務以開始另一個事務。對單獨的助手EJB的調用可能會暫停您的tx並給您相當於REQUIRES_NEW,但我還沒有測試過。


問題的另一半 - 由防禦性編碼解決 - 我要在容器管理的事務,因爲我必須在幾個不同的EJB和EJB方法完成工作的情況。

實體管理器的早期急切刷新允許我捕獲任何驗證錯誤,我的JSR330驗證規則可以找到,所以我只需要確保它們是完整和全面的,所以我從來沒有得到任何檢查約束或完整性違規錯誤從數據庫提交時間。我無法乾淨地處理它們,所以我需要真正防禦它們。該防禦性編碼的

部分是:

  • 大量使用javax.validation註解實體領域,並利用@AssertTrue驗證方法,其中,這是不夠的。
  • 驗證對集合的限制,我很高興看到它的支持。例如,我有一個實體A,集合BA必須有至少有一個B,所以我給B的集合添加了一個@Size(min=1)約束,其中它在A中定義。
  • 自定義JSR330驗證器。我已經爲澳大利亞商業編號(ABNs)等項目添加了幾個自定義驗證器,以確保我從不嘗試向數據庫發送無效數據並觸發數據庫級驗證錯誤。
  • 使用EntityManager.flush()提前刷新實體管理器。這會在驗證發生在代碼的控制之下時發生,而不是稍後JTA開始提交事務。
  • 我的EJB Facade中特定於實體的防禦代碼和邏輯確保JSR330驗證無法檢測到的情況不會出現並導致提交失敗。
  • 在可行的情況下,使用REQUIRES_NEW強制早期提交併允許我在我的EJB中處理故障,並適當地重試等。這有時需要助手EJB來解決與業務方法自我調用有關的問題。

我還是不能很好地處理,然後重試系列化故障或死鎖我就像我使用JDBC時,直接與Swing,所以這一切的「幫助」從容器已經把我倒退數步在某些領域。不過,它在其他地方節省了大量的代碼和邏輯。

發生這些錯誤的地方我添加了一個UI框架級別的異常過濾器。它看到EJBException包裝JTA RollbackException包裝PersistenceException包裝特定於EclipseLink的異常包裝PSQLException,檢查SQLState,並根據它做出決定。這是荒謬的迂迴,但它的作品

2

設置eclipselink.exception-handler屬性指向執行ExceptionHandler看起來很有前途,但沒有解決。

ExceptionHandler的Javadoc ...壞...所以你想看看使用它的test implementation並進行測試(12)。有一些更有用的文檔here

似乎很難使用異常過濾器來處理一些特定的情況,同時使其他所有情況都不受影響。我想陷入PSQLException,檢查SQLSTATE 23514(違反約束的CHECK),爲此拋出一個有用的異常,否則不會改變任何內容。這看起來不實際。

最後,我放棄了這個想法,並儘可能使用bean管理的事務(現在我正確理解它們是如何工作的),並採用防禦方法來防止在使用JTA容器管理事務時出現意外異常。

1

javax.validation.ValidationException是一個JDK異常;我不能 修改它來添加註釋@ApplicationException防止 包裝

除了你的答案:你可以使用XML描述註釋第三方類作爲ApplicationException的。

+0

我不再與此合作(謝天謝地),但是幫助其他仍然存在的人,您有可能添加文檔鏈接的任何機會? – 2013-10-25 02:05:02

+0

使用部署描述符(ejb-jar.xml)。「可以明確指定 應用程序異常部署描述符元素的回滾子元素,以覆蓋 由ApplicationException批註指定或默認的回滾值。」 [JSR 220](http://download.oracle.com/otn-pub/jcp/ejb-3_0-fr-eval-oth-JSpec/ejb-3_0-fr-spec-ejbcore.pdf) – jjmontes 2013-10-25 21:49:02

+1

而[ejb -jar 3.1 Schema](https://www.google.es/url?sa=t&rct=j&q=&esrc=s&source=web&cd=4&ved=0CFMQFjAD&url=http%3A%2F%2Fwww.oracle.com%2Fwebfolder%2Ftechnetwork% 2Fjsc%2Fxml%2Fns%2Fjavaee%2Fejb-jar_3_1.xsd&ei = qulqUvafJ-qg4gTbuoD4CQ&usg = AFQjCNGdH2sjKHnNAmwnKGbFi4eY3vrE1w&sig2 = 5ztOdTlhQ1DDFyizOD1dxg&bvm = bv.55123115,d.bGE)。大多數元素是可選的,以支持部署配置增強。搜索「應用程序例外」。不是最好的文檔,但是我發現在網上很難找到信息(在Oracle之前的版本中似乎更容易)。 – jjmontes 2013-10-25 22:02:42

相關問題