Gestion élégante des violations de contraintes dans l’environnement EJB / JPA?

Je travaille avec EJB et JPA sur un serveur d’applications Glassfish v3. J’ai une classe d’entité où je force l’un des champs à être unique avec une annotation @Column.

@Entity public class MyEntity implements Serializable { private Ssortingng uniqueName; public MyEntity() { } @Column(unique = true, nullable = false) public Ssortingng getUniqueName() { return uniqueName; } public void setUniqueName(Ssortingng uniqueName) { this.uniqueName = uniqueName; } } 

Lorsque j’essaie de conserver un object avec ce champ défini sur une valeur non unique, une exception (comme prévu) est générée lorsque la transaction gérée par le conteneur EJB est validée.

J’ai deux problèmes que j’aimerais résoudre:

1) L’exception que j’obtiens est l’inutile “javax.ejb.EJBException: transaction annulée”. Si j’appelle récursivement assez souvent getCause (), j’atteins finalement l’exception plus utile “java.sql.SQLIntegrityConstraintViolationException”, mais cette exception fait partie de l’implémentation d’EclipseLink et je ne suis pas vraiment à l’aise avec son existence.

Existe-t-il un meilleur moyen d’obtenir des informations d’erreur détaillées avec JPA?

2) Le conteneur EJB insiste pour que cette erreur soit consignée même si je la détecte et la gère.

Existe-t-il un meilleur moyen de gérer cette erreur qui empêchera Glassfish d’encombrer mes journaux avec des informations d’exception inutiles?

Merci.

L’exception que j’obtiens est le peu utile “javax.ejb.EJBException: Transaction annulée”. (…)

J’ai fait un test de mon côté (avec GFv3 et EclipseLink) et je confirme ce comportement. Le stacktrace complet est:

 javax.ejb.EJBException: transaction abandonnée
     at com.sun.ejb.containers.BaseContainer.completeNewTx (BaseContainer.java:4997)
     à l'adresse com.sun.ejb.containers.BaseContainer.postInvokeTx (BaseContainer.java:4756)
     à l'adresse com.sun.ejb.containers.BaseContainer.postInvoke (BaseContainer.java:1955)
     à l'adresse com.sun.ejb.containers.BaseContainer.postInvoke (BaseContainer.java:1906)
     à l'adresse com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke (EJBLocalObjectInvocationHandler.java:198)
     at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke (EJBLocalObjectInvocationHandlerDelegate.java:84)
     at $ Proxy218.myBusinessMethod (Source inconnue)
     at com.stackoverflow.q2522643 .__ EJB31_Generated__MyEJB__Intf ____ Bean __. myBusinessMethod (Source inconnue)
     at com.stackoverflow.q2522643.MyServlet.doGet (MyServlet.java:28)
     à l'adresse javax.servlet.http.HttpServlet.service (HttpServlet.java:734)
     à l'adresse javax.servlet.http.HttpServlet.service (HttpServlet.java:847)
     à 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)
     à org.apache.catalina.core.StandardPipeline.invoke (StandardPipeline.java:641)
     à l'adresse com.sun.enterprise.web.WebPipeline.invoke (WebPipeline.java:97)
     à l'adresse 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)
     à l'adresse com.sun.enterprise.v3.services.impl.ContainerMapper.service (ContainerMapper.java:165)
     à l'adresse com.sun.grizzly.http.ProcessorTask.invokeAdapter (ProcessorTask.java:791)
     à l'adresse com.sun.grizzly.http.ProcessorTask.doProcess (ProcessorTask.java:693)
     à l'adresse com.sun.grizzly.http.ProcessorTask.process (ProcessorTask.java:954)
     à l'adresse com.sun.grizzly.http.DefaultProtocolFilter.execute (DefaultProtocolFilter.java:170)
     at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter (DefaultProtocolChain.java:135)
     à l'adresse com.sun.grizzly.DefaultProtocolChain.execute (DefaultProtocolChain.java:102)
     à com.sun.grizzly.DefaultProtocolChain.execute (DefaultProtocolChain.java:88)
     à l'adresse com.sun.grizzly.http.HttpProtocolChain.execute (HttpProtocolChain.java:76)
     à com.sun.grizzly.ProtocolChainContextTask.doCall (ProtocolChainContextTask.java:53)
     à l'adresse 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)
     à l'adresse com.sun.grizzly.util.AbstractThreadPool $ Worker.run (AbstractThreadPool.java:309)
     sur java.lang.Thread.run (Thread.java:619)
 Causée par: javax.transaction.RollbackException: transaction marquée pour annulation.
     à l'adresse com.sun.enterprise.transaction.JavaEETransactionImpl.commit (JavaEETransactionImpl.java:450)
     à l'adresse com.sun.enterprise.transaction.JavaEETransactionManagerSimplified.commit (JavaEETransactionManagerSimplified.java:837)
     at com.sun.ejb.containers.BaseContainer.completeNewTx (BaseContainer.java:4991)
     ... 34 plus
 Causé par: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DatabaseException
 Exception interne: java.sql.SQLIntegrityConstraintViolationException: l'instruction a été abandonnée car elle aurait entraîné une valeur de clé en double dans une contrainte de clé unique ou principale ou un index unique identifié par 'SQL100326111558470' défini dans 'MYENTITY'.
 Code d'erreur: -1
 Appel: INSERT IN MYENTITY (ID, NAME) VALEURS (?,?)
     bind => [2, Duke!]
 Requête: InsertObjectQuery (com.stackoverflow.q2522643.MyEntity@dba6a9)
     à org.eclipse.persistence.exceptions.DatabaseException.sqlException (DatabaseException.java:324)
     at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect (DatabaseAccessor.java:800)
     à org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeNoSelect (DatabaseAccessor.java:866)
     à 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)
     à org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall (DatasourceCallQueryMechanism.java:205)
     à org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall (DatasourceCallQueryMechanism.java:191)
     à 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)
     à org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.insertObjectForWrite (DatabaseQueryMechanism.java:461)
     à org.eclipse.persistence.queries.InsertObjectQuery.executeCommit (InsertObjectQuery.java:80)
     à org.eclipse.persistence.queries.InsertObjectQuery.executeCommitWithChangeSet (InsertObjectQuery.java:90)
     à org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet (DatabaseQueryMechanism.java:286)
     à org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery (WriteObjectQuery.java:58)
     à org.eclipse.persistence.queries.DatabaseQuery.execute (DatabaseQuery.java:675)
     at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork (DatabaseQuery.java:589)
     à org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery (ObjectLevelModifyQuery.java:109)
     at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork (ObjectLevelModifyQuery.java:86)
     à org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery (UnitOfWorkImpl.java:2863)
     à org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery (AbstractSession.java:1225)
     at 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 (CommitManager.java:103)
     à org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet (AbstractSession.java:3260)
     à 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)
     à l'adresse com.sun.enterprise.transaction.JavaEETransactionImpl.commit (JavaEETransactionImpl.java:412)
     ... 36 plus
 Causée par: java.sql.SQLIntegrityConstraintViolationException: l'instruction a été abandonnée car elle aurait provoqué une valeur de clé en double dans une contrainte de clé unique ou principale ou un index unique identifié par 'SQL100326111558470' défini sur 'MYENTITY'.
     at org.apache.derby.client.am.SQLExceptionFactory40.getSQLException (source inconnue)
     at org.apache.derby.client.am.SqlException.getSQLException (source inconnue)
     at org.apache.derby.client.am.PreparedStatement.executeUpdate (Source inconnue)
     à l'adresse com.sun.gjc.spi.base.PreparedStatementWrapper.executeUpdate (PreparedStatementWrapper.java:108)
     à org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect (DatabaseAccessor.java:791)
     ... 69 plus
 Causée par: org.apache.derby.client.am.SqlException: l'instruction a été abandonnée car elle aurait provoqué une valeur de clé en double dans une contrainte de clé unique ou principale ou un index unique identifié par 'SQL100326111558470' défini dans 'MYENTITY'.
     at org.apache.derby.client.am.Statement.completeExecute (Source inconnue)
     à org.apache.derby.client.net.NetStatementReply.parseEXCSQLSTTreply (Source inconnue)
     à org.apache.derby.client.net.NetStatementReply.readExecute (Source inconnue)
     at org.apache.derby.client.net.StatementReply.readExecute (Source inconnue)
     à org.apache.derby.client.net.NetPreparedStatement.readExecute_ (Source inconnue)
     à org.apache.derby.client.am.PreparedStatement.readExecute (Source inconnue)
     à org.apache.derby.client.am.PreparedStatement.flowExecute (Source inconnue)
     à org.apache.derby.client.am.PreparedStatement.executeUpdateX (source inconnue)
     ... 72 plus

Comme nous pouvons le voir, EclipseLink lève effectivement une oepeDatabaseException qui est ensuite interceptée par le conteneur. Mais c’est faux . EclipseLink devrait émettre une exception PersistenceException (de JPA) ou une exception si sa sous-classe mais certainement pas une exception spécifique du fournisseur. Il s’agit d’ un bogue et vous devez le signaler en tant que tel: https://glassfish.dev.java.net/servlets/ProjectIssues (dans le sous composant entity-persistence ).

Et vous avez absolument raison, vous ne devez PAS intercepter des exceptions spécifiques à un fournisseur pour des raisons de portabilité. Vous devez intercepter une exception JPA PersistenceException ou une sous-classe (et peut-être regarder ensuite l’ SQLException ). Vous devrez peut-être (temporairement) dans ce cas particulier en raison du bogue EclipseLink, mais il s’agit d’une solution de contournement.

Je ne sais pas comment détecter la violation de contrainte unique de manière portable, la meilleure solution que j’ai proposée consiste simplement à faire face à une exception PersistenceException. Si quelqu’un peut répondre, je serais intéressé aussi.

Je peux aider avec le problème du journal.

Dans votre unité de persistance dans votre fichier persistence.xml, ajoutez les éléments suivants:

    

Cela éliminera certaines des exceptions. Vous verrez toujours des traces de stack où le conteneur voit des exceptions au moment de la validation de CMT. Vous devez les avaler avant que le conteneur ne les voie. Vous pouvez faire ce qui suit.

1) Créez une exception spécifique à l’application pour indiquer un problème de persistance. J’ai appelé le mien DataStoreException.

2) N’utilisez pas de bean view sans interface. Ajoutez l’exception DataStoreException à la clause throws de la signature de la méthode dans l’interface biz.

3) Ajoutez la méthode suivante à votre 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; }