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

Я работаю с 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 загромождать мои журналы бесполезной информацией об исключениях?

Спасибо.


person hallidave    schedule 25.03.2010    source источник


Ответы (2)


Исключение, которое я получаю, - это бесполезное «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 < / a>, который затем улавливается контейнером. Но это НЕПРАВИЛЬНО. EclipseLink должен выдать PersistenceException (из JPA) или один, если его подкласс, но определенно не исключение конкретного поставщика. Это ошибка, и вы должны сообщить о ней следующим образом: https://glassfish.dev.java.net/servlets/ProjectIssues (в субкомпоненте entity-persistence).

И вы абсолютно правы, вам не следует перехватывать исключения, связанные с провайдером, ради переносимости. Вы должны поймать JPA PersistenceException или подкласс (а потом, может быть, посмотрите на завернутый SQLException). Возможно, вам придется (временно) в этом конкретном случае из-за ошибки EclipseLink, но это обходной путь.

person Pascal Thivent    schedule 26.03.2010
comment
@hallidave Помните, что PersistenceException сделает недействительным контекст транзакции и откатит транзакцию, независимо от того, поймаете вы это или нет. В javadoc говорится, что все экземпляры PersistenceException, за исключением экземпляров NoResultException, NonUniqueResultException, LockTimeoutException и QueryTimeoutException, заставят текущую транзакцию, если она активна, помечаться для отката .. Это большая разница с обычным JDBC, если вы можете проглотить исключение (в зависимости от того, что вы делаете) и все еще фиксируете. - person ewernli; 26.03.2010
comment
@ewernli Да, я понимаю, что транзакция откатывается, несмотря ни на что - на самом деле это просто журнал, полный трассировок стека, который меня раздражает. В идеале я хотел бы перехватить исключение внутри контекста EJB, но оно не возникает, пока контейнер не зафиксирует транзакцию. Я думаю о том, чтобы просто управлять транзакцией самостоятельно, чтобы иметь больший контроль. - person hallidave; 26.03.2010
comment
Почему было бы лучше, если бы EclipseLink выдал PersistenceException? У вас не было возможности выяснить, что, черт возьми, произошло (какое поле нарушает уникальное ограничение (у вас могло бы быть более уникальное поле в Entity)) - person pihentagy; 26.07.2010
comment
@pihentagy: Странные вопросы. Потому что так говорится в спецификации, потому что это очевидный способ написания переносимого кода. - person Pascal Thivent; 27.07.2010
comment
Я отправил сообщение об ошибке по этой проблеме. Проголосуйте за него, чтобы мы могли решить эту проблему: bugs.eclipse.org/ bugs / show_bug.cgi? id = 375745 - person sdoca; 30.03.2012
comment
В моем случае, поскольку мы не можем выяснить, в чем причина PersistenceException, я сначала пытаюсь загрузить запись, соответствующую ограничению. В случае успеха я могу выдать значимое исключение, например «Сущность уже существует». В противном случае я могу продолжить сохранение нового объекта и уверен, что не получу исключение. Это обходной путь, требующий дополнительного выбора базы данных. И есть небольшая вероятность, что другой поток сохранит запись с такими же значениями между select и persist. - person Buka; 05.04.2019

Я не знаю, как обнаружить нарушение уникального ограничения переносимым способом, лучшее, что я придумал, - это просто иметь дело с 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;
}
person Chase    schedule 02.04.2010