Элегантно обрабатывать нарушения ограничений в среде EJB / JPA? - PullRequest
18 голосов
/ 26 марта 2010

Я работаю с EJB и JPA на сервере приложений Glassfish v3. У меня есть класс Entity, где я заставляю одно из полей быть уникальным с помощью аннотации @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: Transaction aborted». Если я рекурсивно вызываю getCause () достаточное количество раз, я в конце концов достигаю более полезного «java.sql.SQLIntegrityConstraintViolationException», но это исключение является частью реализации EclipseLink, и мне не очень удобно полагаться на его существование.

Есть ли лучший способ получить подробную информацию об ошибках с помощью JPA?

2) Контейнер EJB настаивает на регистрации этой ошибки, даже если я ее улавливаю и обрабатываю.

Есть ли лучший способ справиться с этой ошибкой, которая не позволит Glassfish загромождать мои журналы бесполезной информацией об исключениях?

Спасибо.

Ответы [ 2 ]

22 голосов
/ 26 марта 2010

Исключением, которое я получаю, является бесполезное «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(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, но это обходной путь.

5 голосов
/ 02 апреля 2010

Я не знаю, как обнаружить уникальное нарушение ограничения переносимым способом, лучшее, что я придумал, это просто иметь дело с PersistenceException. Если кто-то может ответить, мне тоже будет интересно.

Я могу помочь с проблемой журнала.

Внутри вашего модуля персистентности в файле persistence.xml добавьте следующее:

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

Это избавит от некоторых исключений. Вы по-прежнему будете видеть трассировки стека, где контейнер видит исключения во время фиксации CMT. Вы должны проглотить их, прежде чем контейнер увидит их. Вы можете сделать следующее.

1) Создайте исключение для конкретного приложения, чтобы указать на проблему с постоянством. Я вызвал мое DataStoreException.

2) Не используйте бин вида без интерфейса. Добавьте 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;
}
...