Java 在 EJB/JPA 环境中优雅地处理约束违规?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2519902/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Elegantly handling constraint violations in EJB/JPA environment?
提问by hallidave
I'm working with EJB and JPA on a Glassfish v3 app server. I have an Entity class where I'm forcing one of the fields to be unique with a @Column annotation.
我正在 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;
}
}
When I try to persist an object with this field set to a non-unique value I get an exception (as expected) when the transaction managed by the EJB container commits.
当我尝试将此字段设置为非唯一值的对象持久化时,当 EJB 容器管理的事务提交时,我收到异常(如预期)。
I have two problems I'd like to solve:
我有两个问题想解决:
1) The exception I get is the unhelpful "javax.ejb.EJBException: Transaction aborted". If I recursively call getCause() enough times, I eventually reach the more useful "java.sql.SQLIntegrityConstraintViolationException", but this exception is part of the EclipseLink implementation and I'm not really comfortable relying on it's existence.
1) 我得到的异常是无用的“javax.ejb.EJBException: Transaction aborted”。如果我递归调用 getCause() 的次数足够多,我最终会得到更有用的“java.sql.SQLIntegrityConstraintViolationException”,但是这个异常是 EclipseLink 实现的一部分,我不太愿意依赖它的存在。
Is there a better way to get detailed error information with JPA?
有没有更好的方法来使用 JPA 获取详细的错误信息?
2) The EJB container insists on logging this error even though I catch it and handle it.
2) EJB 容器坚持记录此错误,即使我捕获并处理它。
Is there a better way to handle this error which will stop Glassfish from cluttering up my logs with useless exception information?
有没有更好的方法来处理这个错误,这将阻止 Glassfish 用无用的异常信息弄乱我的日志?
Thanks.
谢谢。
采纳答案by Pascal Thivent
The exception I get is the unhelpful "javax.ejb.EJBException: Transaction aborted". (...)
我得到的异常是无用的“javax.ejb.EJBException: Transaction aborted”。(……)
I did a test on my side (with GFv3 and EclipseLink) and I confirm this behavior. The full stacktrace is :
我在我这边做了一个测试(使用 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(com.stackoverflow.q2522643.MyEntity@dba6a9) 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
As we can see, EclipseLink actually throws a o.e.p.e.DatabaseException
which is then caught by the container. But this is WRONG. EclipseLink should throw a PersistenceException
(from JPA) or one if its subclass but certainly not a provider specific exception. This isa bug and you should report it as such: https://glassfish.dev.java.net/servlets/ProjectIssues(in the entity-persistencesubcomponent).
正如我们所看到的,EclipseLink 实际上抛出了一个o.e.p.e.DatabaseException
,然后被容器捕获。但这是错误的。EclipseLink 应该抛出一个PersistenceException
(来自 JPA)或一个,如果它的子类,但肯定不是提供者特定的异常。这是一个错误,您应该将其报告为:https: //glassfish.dev.java.net/servlets/ProjectIssues(在实体持久性子组件中)。
And you're absolutely right, you should NOTcatch provider specific exceptions for the sake of portability. You should catch a JPA PersistenceException
or a subclass (and then maybe look at the wrapped SQLException
). You may have to (temporarily) in this particular case because of the EclipseLink bug, but this is a workaround.
你是绝对正确的,你应该不赶供应商特定的例外便携性的缘故。您应该捕获 JPAPersistenceException
或子类(然后可能查看包装的SQLException
)。由于 EclipseLink 错误,您可能必须(暂时)在这种特殊情况下这样做,但这是一种解决方法。
回答by Chase
I don't know how to detect the unique constraint violation in a portable way, the best I've come up with is just dealing with a PersistenceException. If someone can answer that I'd be interested too.
我不知道如何以可移植的方式检测唯一约束违规,我想出的最好的方法就是处理 PersistenceException。如果有人能回答我也会感兴趣。
I can help with the log issue.
我可以帮助解决日志问题。
Inside of your persistence unit in your persistence.xml add the following:
在您的persistence.xml 中的持久性单元内添加以下内容:
<properties>
<property name="eclipselink.logging.level" value="SEVERE"/>
</properties>
That will get rid of some of the exceptions. You'll still see stack traces where the container is seeing exceptions at CMT commit time. You have to swallow these before the container sees them. You can do the following.
这将消除一些例外情况。您仍然会看到容器在 CMT 提交时看到异常的堆栈跟踪。你必须在容器看到它们之前吞下它们。您可以执行以下操作。
1) Create an application specific exception to indicate a persistence problem. I called mine DataStoreException.
1) 创建特定于应用程序的异常以指示持久性问题。我调用了我的 DataStoreException。
2) Don't use a no-interface view bean. Add the DataStoreException to the throws clause of the method signature in the biz interface.
2) 不要使用无接口的视图 bean。在 biz 接口中方法签名的 throws 子句中添加 DataStoreException。
3) Add the following method to your EJB:
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;
}