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

Благодарю.

2 ответа

Решение

Исключением, которое я получаю, является бесполезное "javax.ejb.EJBException: Transaction aborted". (...)

Я сделал тест на моей стороне (с GFv3 и EclipseLink), и я подтверждаю это поведение. Полная трассировка стека:

javax.ejb.EJBException: транзакция прервана в com.sun.ejb.containers.BaseContainer.completeNewTx(BaseContainer.java:4997) в com.sun.ejb.containers.BaseContainer.postInvokeTx(BaseContainer.java:4756) в com.sun.ejb.containers.BaseContainer. 198 q2522643.MyServlet.doGet(MyServlet.java:28) в javax.servlet.http.HttpServlet.service(HttpServlet.java:734) в javax.servlet.http.HttpServlet.service (HttpServlet) или HttpServlet.java)
    ..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.apcapal.cat..invoke(StandardPipeline.java:641) на com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:97) на com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:)
    .apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:185) в org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:332) в org.apache.catalina.connector.serviceteryapter. Java:233) на com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:165) на com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:791) на com.sun..grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:693) в com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:954) в com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:170) в com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:135) в com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain):.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88) по адресу com.sun.grizzly.http. com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57) в com.sun.grizzly.ContextTask.run(ContextTask.java:69) в com.sun.grizzly.util.AbstractThreadPool$Worker.doWok Abstract. Java:330) на com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:309) на java.lang.Thread.run(Thread.java:619)
    . Причина: javax.transaction.RollbackException: транзакция отмечен для отката. по адресу com.sun.enterprise.transaction.JavaEETransactionImpl.commit(JavaEETransactionImpl.java:450) по адресу com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.commit(JavaEETransactionManagerSimplified.java:837) по адресу com.sunererstej.base (BaseContainer.java:4991)
    ... еще 34 причины: Исключение [EclipseLink-4002] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DatabaseException Внутреннее исключение: java.sql.SQLIntegrityConstraintViolationException: оператор был прерван, поскольку он вызвал бы дублирование значения ключа в ограничении уникального или первичного ключа или уникальном индексе, идентифицированном как "SQL100326111558470", определенном в "MYENTITY". Код ошибки: -1 Вызов: INSERT INTO MYENTITY (ID, NAME) VALUES (?,?) Bind => [2, Duke!] Запрос: InsertObjectQuery(com.stackru.q2522643.MyEntity@dba6a9) в 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.ecu.java:866) на org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:586) на org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:529) на org.eclipse.persistence.internal.sessions.AbstractSession.executeCall(AbstractSession.java:914) в org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:20lin.se..sa..DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:191) по адресу org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.insertObject(DatasourceCallQueryMechanism.java:334) по адресу org.eclipse.persism.chanj.eqQuject.uu в org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:177) в org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.insertObjectForWrite(DatabaseQueryMechanism.ava) orj..InsertObjectQuery.executeCommit(InsertObjectQuery.java:80) по адресу org.eclipse.persistence.queries.InsertObjectQuery.executeCommitWithChangeSet(InsertObjectQuery.java:90) по адресу org.eclipse.persery.DecanWeWeWithWiteWuQuqu) в org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58) в org.eclipse.persistence.queries.DatabaseQuery.exe милый (DatabaseQuery.java:675) в org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:589) в org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOlj.WeWeWW.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:86) в org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl. (AbstractSession.java:1225) в org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1207) в org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1167) в org.eclipse.persistence.internal.sessions.CommitManager.commitNewObjectsForClassWithChangeSet(CommitManager.java:197) в org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManan ava:103)
    at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:3260) в org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWava.lg)405.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitToDatabase(RepeatableWriteUnitOfWork.java:547) в org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChange..issueSQLbeforeCompletion(UnitOfWorkImpl.java:3134) при org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.issueSQLbeforeCompletion(RepeatableWriteUnitOfWork.java:268) при org.eclipse.persistence.transaction.AbstractSynchronizationListener.beforeCompletion(AbstractSynchronizationListener.java:157) в org.eclipse.persistence.transaction.JTASynchronizationListener.beforeCompletion(JTASynchronizationListener.java:68) в com.sun.enterprise.transaction.JavaEETransactionImpl.commit(JavaEETransactionImpl.java:412)
    ... еще 36. Причина: java.sql.SQLIntegrityConstraintViolationException: Оператор был прерван, так как он вызвал бы дублирование значения ключа в ограничении уникального или первичного ключа или уникального индекса, определяемого как 'SQL100326111558470'определено для'MYENTITY'. в org.apache.derby.client.am.SQLExceptionFactory40.getSQLException(неизвестный источник) в org.apache.derby.client.am.SqlException.getSQLException(неизвестный источник) в org.apache.derby.client.am.PreparedStatep.atete (Неизвестный источник) в com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate(PreparedStatementWrapper.java:108) в org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:79) больше Причина: org.apache.derby.client.am.SqlException: оператор был прерван, так как он вызвал бы дублирование значения ключа в ограничении уникального или первичного ключа или уникальном индексе, определенном в "SQL100326111558470", определенном в "MYENTITY". в 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 на самом деле бросает o.e.p.e.DatabaseExceptionкоторый затем пойман контейнером. Но это НЕПРАВИЛЬНО. EclipseLink должен бросить PersistenceException (от JPA) или один, если его подкласс, но, конечно, не исключение для конкретного поставщика. Это ошибка, и вы должны сообщить о ней следующим образом: https://glassfish.dev.java.net/servlets/ProjectIssues (в подкомпоненте сохранения сущности) .

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

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