2010-03-25 197 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; 
}