优雅地处理EJB / JPA环境中的约束违规?
我在Glassfish v3应用服务器上使用EJB和JPA。 我有一个实体类,我强迫其中一个字段与@Column注释是唯一的。
@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容器管理的事务提交时,我得到exception(如预期的那样)。
我有两个问题需要解决:
1)我得到的exception是无用的“javax.ejb.EJBException:Transaction aborted”。 如果我递归调用getCause()足够多次,我最终会得到更有用的“java.sql.SQLIntegrityConstraintViolationException”,但是这个exception是EclipseLink实现的一部分,我不太愿意依赖它的存在。
有没有更好的方法来获取JPA的详细错误信息?
2)EJB容器坚持记录此错误,即使我抓住它并处理它。
有没有更好的方法来处理这个错误,这将阻止Glassfish使用无用的exception信息混乱我的日志?
谢谢。
我得到的例外是无用的“javax.ejb.EJBException:Transaction aborted”。 (……)
我在我身边做了一个测试(用GFv3和EclipseLink),我确认了这个行为。 完整的堆栈跟踪是:
javax.ejb.EJBException:事务已中止 在com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:4997) at com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4756) 在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) 在$ Proxy218.myBusinessMethod(未知来源) at com.stackoverflow.q2522643 .__ EJB31_Generated__MyEJB__Intf ____ Bean __。myBusinessMethod(Unknown Source) 在com.stackoverflow.q2522643.MyServlet.doGet(MyServlet.java:28) 在javax.servlet.http.HttpServlet.service(HttpServlet.java:734) 在javax.servlet.http.HttpServlet.service(HttpServlet.java:847) 在org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1523) 在org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:279) 在org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:188) 在org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:641) 在com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:97) at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:85) 在org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:185) 在org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:332) 在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) 在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) 在com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:102) 在com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88) 在com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76) at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:53) 在com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57) 在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) 在java.lang.Thread.run(Thread.java:619) 引起:javax.transaction.RollbackException:标记为回滚的事务。 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个 引起:Exception [EclipseLink-4002](Eclipse Persistence Services - 2.0.0.v20091127-r5931):org.eclipse.persistence.exceptions.DatabaseException 内部exception:java.sql.SQLIntegrityConstraintViolationException:语句已中止,因为它会在“MYENTITY”中定义的“SQL100326111558470”标识的唯一或主键约束或唯一索引中导致重复键值。 错误代码:-1 呼叫:插入MYENTITY(ID,NAME)值(?,?) bind => [2,Duke!] 查询:InsertObjectQuery(com.stackoverflow.q2522643.MyEntity@dba6a9) at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:324) 在org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:800) 在org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeNoSelect(DatabaseAccessor.java:866) 在org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:586) 在org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:529) at org.eclipse.persistence.internal.sessions.AbstractSession.executeCall(AbstractSession.java:914) 在org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:205) 在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) 在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) 在org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:286) at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58) 在org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:675) 在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) 在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) 在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个 引起:java.sql.SQLIntegrityConstraintViolationException:语句已中止,因为它会在“MYENTITY”中定义的“SQL100326111558470”标识的唯一或主键约束或唯一索引中导致重复键值。 at org.apache.derby.client.am.SQLExceptionFactory40.getSQLException(未知来源) at org.apache.derby.client.am.SqlException.getSQLException(未知来源) 在org.apache.derby.client.am.PreparedStatement.executeUpdate(未知来源) at com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:108) 在org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:791) ......还有69个 由以下原因引起:org.apache.derby.client.am.SqlException:语句已中止,因为它会在由'MYENTITY'上定义的'SQL100326111558470'标识的唯一或主键约束或唯一索引中导致重复键值。 在org.apache.derby.client.am.Statement.completeExecute(未知来源) 在org.apache.derby.client.net.NetStatementReply.parseEXCSQLSTTreply(未知来源) 在org.apache.derby.client.net.NetStatementReply.readExecute(未知来源) 在org.apache.derby.client.net.StatementReply.readExecute(未知来源) 在org.apache.derby.client.net.NetPreparedStatement.readExecute_(未知来源) 在org.apache.derby.client.am.PreparedStatement.readExecute(未知来源) 在org.apache.derby.client.am.PreparedStatement.flowExecute(未知来源) 在org.apache.derby.client.am.PreparedStatement.executeUpdateX(未知来源) ......还有72个
正如我们所看到的,EclipseLink实际上抛出了一个oepeDatabaseException
,然后由容器捕获。 但这是错误的 。 EclipseLink应该抛出一个PersistenceException
(来自JPA)或者它的子类,但肯定不是特定于提供者的exception。 这是一个错误,你应该这样报告: https : //glassfish.dev.java.net/servlets/ProjectIssues (在实体持久性子组件中)。
而且你是绝对正确的,为了便携性,你不应该捕获提供者特定的例外。 您应该捕获JPA PersistenceException
或子类(然后可能会查看包装的SQLException
)。 由于EclipseLink错误,您可能必须(暂时)在此特定情况下,但这是一种解决方法。
我不知道如何以可移植的方式检测唯一约束违规,我提出的最好的只是处理PersistenceException。 如果有人能回答我也会感兴趣的话。
我可以帮助解决日志问题。
在persistence.xml中的持久性单元内添加以下内容:
这将摆脱一些例外。 您仍将看到容器在CMT提交时看到exception的堆栈跟踪。 你必须在容器看到它们之前吞下它们。 您可以执行以下操作。
1)创建特定于应用程序的exception以指示持久性问题。 我调用了我的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; }