2010-03-25 198 views
17

我正在Glassfish v3應用服務器上使用EJB和JPA。我有一個實體類,我迫使其中一個字段與@Column註釋是唯一的。在EJB/JPA環境中優雅處理約束違規?

@Entity 
public class MyEntity implements Serializable { 

    private String uniqueName; 

    public MyEntity() { 
    } 

    @Column(unique = true, nullable = false) 
    public String getUniqueName() { 
     return uniqueName; 
    } 

    public void setUniqueName(String uniqueName) { 
     this.uniqueName = uniqueName; 
    } 
} 

當我嘗試堅持與此字段設置爲一個非唯一值我得到一個異常的對象(如預期)時由EJB容器管理的事務提交。

我有兩個問題,我想解答:

1)我得到的例外是無益的「javax.ejb.EJBException異常:事務終止」。如果我遞歸地調用getCause()足夠多次,我最終會達到更有用的「java.sql.SQLIntegrityConstraintViolationException」,但是這個異常是EclipseLink實現的一部分,我不太喜歡依賴它的存在。

有更好的方法來獲得JPA的詳細錯誤信息嗎?

2)EJB容器堅持記錄這個錯誤,即使我抓住並處理它。

有沒有更好的方法來處理這個錯誤,這將阻止Glassfish使用無用的異常信息弄亂我的日誌?

謝謝。

回答

22

我得到的異常是無用的「javax.ejb.EJBException:Transaction aborted」。 (...)

我做了一個測試(與GFv3和EclipseLink),我確認了這種行爲。完整的堆棧跟蹤是:

 
javax.ejb.EJBException: Transaction aborted 
    at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:4997) 
    at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4756) 
    at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1955) 
    at com.sun.ejb.containers.BaseContainer.postInvoke(BaseContainer.java:1906) 
    at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:198) 
    at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:84) 
    at $Proxy218.myBusinessMethod(Unknown Source) 
    at com.stackoverflow.q2522643.__EJB31_Generated__MyEJB__Intf____Bean__.myBusinessMethod(Unknown Source) 
    at com.stackoverflow.q2522643.MyServlet.doGet(MyServlet.java:28) 
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:734) 
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:847) 
    at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1523) 
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:279) 
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:188) 
    at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:641) 
    at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:97) 
    at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:85) 
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:185) 
    at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:332) 
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:233) 
    at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:165) 
    at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:791) 
    at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:693) 
    at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:954) 
    at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:170) 
    at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:135) 
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:102) 
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88) 
    at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76) 
    at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:53) 
    at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57) 
    at com.sun.grizzly.ContextTask.run(ContextTask.java:69) 
    at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:330) 
    at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:309) 
    at java.lang.Thread.run(Thread.java:619) 
Caused by: javax.transaction.RollbackException: Transaction marked for rollback. 
    at com.sun.enterprise.transaction.JavaEETransactionImpl.commit(JavaEETransactionImpl.java:450) 
    at com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.commit(JavaEETransactionManagerSimplified.java:837) 
    at com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:4991) 
    ... 34 more 
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DatabaseException 
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'SQL100326111558470' defined on 'MYENTITY'. 
Error Code: -1 
Call: INSERT INTO MYENTITY (ID, NAME) VALUES (?, ?) 
    bind => [2, Duke!] 
Query: InsertObjectQuery([email protected]) 
    at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:324) 
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:800) 
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeNoSelect(DatabaseAccessor.java:866) 
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:586) 
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:529) 
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeCall(AbstractSession.java:914) 
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:205) 
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:191) 
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.insertObject(DatasourceCallQueryMechanism.java:334) 
    at org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:162) 
    at org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:177) 
    at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.insertObjectForWrite(DatabaseQueryMechanism.java:461) 
    at org.eclipse.persistence.queries.InsertObjectQuery.executeCommit(InsertObjectQuery.java:80) 
    at org.eclipse.persistence.queries.InsertObjectQuery.executeCommitWithChangeSet(InsertObjectQuery.java:90) 
    at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:286) 
    at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58) 
    at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:675) 
    at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:589) 
    at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:109) 
    at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:86) 
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2863) 
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1225) 
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1207) 
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1167) 
    at org.eclipse.persistence.internal.sessions.CommitManager.commitNewObjectsForClassWithChangeSet(CommitManager.java:197) 
    at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:103) 
    at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:3260) 
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1405) 
    at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitToDatabase(RepeatableWriteUnitOfWork.java:547) 
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1510) 
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.issueSQLbeforeCompletion(UnitOfWorkImpl.java:3134) 
    at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.issueSQLbeforeCompletion(RepeatableWriteUnitOfWork.java:268) 
    at org.eclipse.persistence.transaction.AbstractSynchronizationListener.beforeCompletion(AbstractSynchronizationListener.java:157) 
    at org.eclipse.persistence.transaction.JTASynchronizationListener.beforeCompletion(JTASynchronizationListener.java:68) 
    at com.sun.enterprise.transaction.JavaEETransactionImpl.commit(JavaEETransactionImpl.java:412) 
    ... 36 more 
Caused by: java.sql.SQLIntegrityConstraintViolationException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'SQL100326111558470' defined on 'MYENTITY'. 
    at org.apache.derby.client.am.SQLExceptionFactory40.getSQLException(Unknown Source) 
    at org.apache.derby.client.am.SqlException.getSQLException(Unknown Source) 
    at org.apache.derby.client.am.PreparedStatement.executeUpdate(Unknown Source) 
    at com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:108) 
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:791) 
    ... 69 more 
Caused by: org.apache.derby.client.am.SqlException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'SQL100326111558470' defined on 'MYENTITY'. 
    at org.apache.derby.client.am.Statement.completeExecute(Unknown Source) 
    at org.apache.derby.client.net.NetStatementReply.parseEXCSQLSTTreply(Unknown Source) 
    at org.apache.derby.client.net.NetStatementReply.readExecute(Unknown Source) 
    at org.apache.derby.client.net.StatementReply.readExecute(Unknown Source) 
    at org.apache.derby.client.net.NetPreparedStatement.readExecute_(Unknown Source) 
    at org.apache.derby.client.am.PreparedStatement.readExecute(Unknown Source) 
    at org.apache.derby.client.am.PreparedStatement.flowExecute(Unknown Source) 
    at org.apache.derby.client.am.PreparedStatement.executeUpdateX(Unknown Source) 
    ... 72 more 

正如我們所看到的,實際上的EclipseLink拋出一個o.e.p.e.DatabaseException,然後由容器抓住了。但這是錯誤。 EclipseLink應該拋出一個PersistenceException(來自JPA)或一個如果它的子類,但肯定不是提供者特定的異常。這一個錯誤,你應該這樣報告:https://glassfish.dev.java.net/servlets/ProjectIssues(在實體持久子組件)。

而你是絕對正確的,你應該不是捕獲供應商特定的例外,爲了便於攜帶。你應該趕上JPA PersistenceException或者一個子類(然後看看包裝的SQLException)。由於EclipseLink錯誤,您可能必須(暫時)在這種特殊情況下,但這是一種解決方法。

+0

@hallidave當心了'PersistenceException'會作廢事務上下文和回滾事務,如果趕上與否*沒有關係*。 javadoc說:「除了NoResultException,NonUniqueResultException,LockTimeoutException和QueryTimeoutException的實例外,所有PersistenceException實例都將導致當前事務(如果有的話)被標記爲回滾。」這與普通的JDBC有很大的區別,你可以吞下一個異常(取決於你所做的)並仍然提交。 – ewernli 2010-03-26 11:32:41

+0

@ewernli是的,我知道無論怎樣,事務都會回滾 - 這實際上就是我發現煩人的堆棧記錄。理想情況下,我想在EJB的上下文中捕獲異常,但在容器提交事務之前不會發生。我正在考慮自己管理交易,所以我有更多的控制權。 – hallidave 2010-03-26 15:00:49

+0

如果EclipseLink拋出一個'PersistenceException',爲什麼它會更好?你沒有機會弄清楚到底發生了什麼(哪個字段違反了一個獨特的約束條件(你可能在一個實體中有更多獨特的字段)) – pihentagy 2010-07-26 16:03:01

5

我不知道如何以便攜的方式檢測唯一的約束違規,最好的我只是處理一個PersistenceException。如果有人能回答我也會感興趣。

我可以幫助解決日誌問題。

內,您的持久性單元在persistence.xml中添加以下內容:

<properties> 
    <property name="eclipselink.logging.level" value="SEVERE"/> 
</properties> 

這將擺脫一些例外。您仍然會看到堆棧跟蹤,容器在CMT提交時看到異常。你必須在容器看到它們之前吞下它們。您可以執行以下操作。

1)創建一個特定於應用程序的異常來指示持久性問題。我打電話給我的DataStoreException。

2)不要使用無界面視圖bean。將DataStoreException添加到biz界面中方法簽名的throws子句。

3)添加以下方法給您的EJB:

@AroundInvoke 
public Object interceptor(InvocationContext ic) throws Exception { 
    Object o = null; 
    try { 
     o = ic.proceed(); 
     if (!sessionContext.getRollbackOnly()) { 
      entityManager.flush(); 
     } 
    } catch (PersistenceException ex) { 
     throw new DataStoreException(ex); 
    } 
    return o; 
}