Я работаю с EJB и JPA на сервере приложений Glassfish v3. У меня есть класс Объекта, где я вынуждаю одно из полей быть уникальным с @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, фиксирует.
У меня есть две проблемы, которые я хотел бы решить:
1) Исключением, которое я получаю, является бесполезное "javax.ejb. EJBException: Транзакция прерывается". Если я рекурсивно называю getCause () достаточно раз, я в конечном счете достигаю более полезного "java.sql. SQLIntegrityConstraintViolationException", но это исключение является частью реализации EclipseLink, и я не действительно удобное доверие, это - существование.
Существует ли лучший способ получить подробную ошибочную информацию с JPA?
2) Контейнер EJB настаивает на том, чтобы регистрировать эту ошибку даже при том, что я ловлю его и обрабатываю его.
Существует ли лучший способ обработать эту ошибку, которая будет мешать Glassfish загромоздить мои журналы с бесполезной информацией об исключении?
Спасибо.
Исключение, которое я получаю, - это бесполезное исключение "javax.ejb.EJBException: Транзакция прервана ». (...)
Я провел тест на своей стороне (с 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
Как мы видим, EclipseLink фактически генерирует исключение o.e.p.e.DatabaseException
, которое затем перехватывается контейнером. Но это НЕПРАВИЛЬНО . EclipseLink должен генерировать исключение PersistenceException
(из JPA) или одно, если его подкласс, но определенно не исключение, специфичное для поставщика. Это является ошибкой, и вы должны сообщить о ней как о таковой: https://glassfish.dev.java.net/servlets/ProjectIssues (в entity-persistence ] подкомпонент).
И вы абсолютно правы, вы должны НЕ перехватывать исключения, специфичные для провайдера, ради переносимости. Вы должны поймать JPA PersistenceException
или подкласс (а затем, возможно, посмотреть на обернутый SQLException
). Возможно, вам придется (временно) в этом конкретном случае из-за ошибки EclipseLink, но это обходной путь.
Я не знаю, как обнаружить нарушение уникального ограничения переносимым способом, лучшее, что я придумал, - это просто иметь дело с PersistenceException. Если кто-то может ответить, мне тоже будет интересно.
Я могу помочь с проблемой журнала.
Внутри модуля сохранения состояния в файле persistence.xml добавьте следующее:
<properties>
<property name="eclipselink.logging.level" value="SEVERE"/>
</properties>
Это позволит избавиться от некоторых исключений. Вы по-прежнему будете видеть трассировки стека, когда контейнер видит исключения во время фиксации CMT. Вы должны проглотить их, прежде чем контейнер их увидит. Вы можете сделать следующее.
1) Создайте исключение для конкретного приложения, чтобы указать на проблему с сохранением. Я назвал свое исключение DataStoreException.
2) Не используйте bean-компонент представления без интерфейса. Добавьте DataStoreException в предложение throws сигнатуры метода в интерфейсе biz.
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;
}