2010-03-25 12 views
17

Estoy trabajando con EJB y JPA en un servidor de aplicaciones Glassfish v3. Tengo una clase Entity en la que estoy forzando que uno de los campos sea único con una anotación @Column.¿Maneja elegantemente violaciones de restricciones en el entorno EJB/JPA?

@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; 
    } 
} 

Cuando trato de persistir un objeto con este campo se establece en un valor no único consigo una excepción (como se esperaba) cuando la transacción gestionada por el contenedor EJB se compromete.

que tienen dos problemas que me gustaría resolver:

1) La excepción que se ve es la inútil "javax.ejb.EJBException: transacción abortada". Si llamo recursivamente a getCause() bastantes veces, eventualmente llego a la más útil "java.sql.SQLIntegrityConstraintViolationException", pero esta excepción forma parte de la implementación de EclipseLink y no me siento cómodo confiando en su existencia.

¿Existe alguna manera mejor de obtener información detallada de error con JPA?

2) El contenedor EJB insiste en registrar este error aunque lo atrape y lo maneje.

¿Existe una forma mejor de manejar este error que evitará que Glassfish abarrote mis registros con información de excepción inútil?

Gracias.

Respuesta

22

La excepción que recibo es la inútil "javax.ejb.EJBException: transacción cancelada". (...)

Hice una prueba de mi parte (con GFv3 y EclipseLink) y confirmo este comportamiento. El StackTrace completa es:

 
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([email protected]) 
    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 

Como podemos ver, en realidad EclipseLink tiros un o.e.p.e.DatabaseException que luego es atrapado por el contenedor. Pero esto es INCORRECTO. EclipseLink debería lanzar un PersistenceException (desde JPA) o uno si su subclase, pero ciertamente no es una excepción específica del proveedor. Este es un error y debe informarlo como tal: https://glassfish.dev.java.net/servlets/ProjectIssues (en el entidad-persistencia subcomponente).

Y tiene toda la razón, debe NO atrapar excepciones específicas del proveedor en aras de la portabilidad. Debería capturar un JPA PersistenceException o una subclase (y luego tal vez mirar el SQLException envuelto). Es posible que tenga que (temporalmente) en este caso particular debido al error EclipseLink, pero esto es una solución.

+0

@hallidave Mira que un 'PersistenceException' invalidará el contexto de transacciones y retrotraer la transacción, * no importa si lo detecta o no *. El javadoc dice "Todas las instancias de PersistenceException, excepto las instancias de NoResultException, NonUniqueResultException, LockTimeoutException y QueryTimeoutException, provocarán que la transacción actual, si una está activa, se marque para la reversión". Esa es una gran diferencia con JDBC simple si puedes tragar una excepción (dependiendo de lo que hagas) y aún cometer. – ewernli

+0

@ewernli Sí, entiendo que la transacción se revierte sin importar nada; en realidad es solo el registro lleno de stacktraces que encuentro molesto. Idealmente, me gustaría capturar la excepción dentro del contexto del EJB, pero no ocurre hasta que el contenedor confirma la transacción. Estoy pensando en administrar la transacción solo para tener más control. – hallidave

+0

¿Por qué sería mejor si EclipseLink lanzara una 'PersistenceException'? No tuvo oportunidad de descubrir qué diablos pasó (qué campo viola una restricción única (podría tener más campo único en una Entidad)) – pihentagy

5

No sé cómo detectar la violación de restricción única de una manera portátil, lo mejor que se me ocurre es tratar con una PersistenceException. Si alguien puede responder, yo también estaría interesado.

Puedo ayudar con el problema de registro.

interior de su unidad de persistencia en sus persistence.xml añadir lo siguiente:

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

que logrará erradicar de algunas de las excepciones. Todavía verá rastros de pila donde el contenedor está viendo excepciones en CMT commit time.Tienes que tragar estos antes de que el contenedor los vea. Puedes hacer lo siguiente.

1) Cree una excepción específica de la aplicación para indicar un problema de persistencia. Llamé a la mía DataStoreException.

2) No utilice un bean de vista sin interfaz. Agregue DataStoreException a la cláusula throws de la firma del método en la interfaz de biz.

3) Añadir el siguiente método para 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; 
} 
Cuestiones relacionadas