web-dev-qa-db-fra.com

Appelez getNextException pour en connaître la cause: Comment faire en sorte que Hibernate/JPA affiche le message du serveur de base de données pour une exception

J'utilise Postgresql, Hibernate et JPA. Chaque fois qu'il y a une exception dans la base de données, j'obtiens quelque chose comme ceci qui n'est pas très utile car cela ne montre pas ce qui s'est vraiment passé sur le serveur de base de données.

Caused by: Java.sql.BatchUpdateException: Batch entry 0 update foo set ALERT_FLAG='3' was aborted.  Call getNextException to see the cause.
    at org.postgresql.jdbc2.AbstractJdbc2Statement$BatchResultHandler.handleError(AbstractJdbc2Statement.Java:2621)
    at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.Java:1837)
    at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.Java:407)
    at org.postgresql.jdbc2.AbstractJdbc2Statement.executeBatch(AbstractJdbc2Statement.Java:2754)
    at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeBatch(NewProxyPreparedStatement.Java:1723)
    at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.Java:70)
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.Java:268)
    ... 82 more

Je veux que le message d'exception de la base de données apparaisse dans le journal de l'application.

Je suis tombé sur cet article qui utilise un Aspect pour renseigner la chaîne d'exceptions qui, sinon, ne serait pas renseignée correctement en cas d'exception SQLExceptions.

Y at-il un moyen de résoudre ce problème sans utiliser Aspects ou tout code personnalisé. La solution idéale impliquerait uniquement des modifications de fichiers de configuration.

26
Dojo

Pour ce faire, il n'est pas nécessaire d'écrire de code personnalisé. Hibernate enregistre la cause de l'exception par défaut. Si vous ne le voyez pas, la journalisation Hibernate ne doit pas être configurée correctement. Voici un exemple avec slf4j + log4j et l'utilisation de Maven pour la gestion des dépendances.

src/main/Java/pgextest/PGExceptionTest.Java

public class PGExceptionTest {

    public static void main(String[] args) throws Exception {

        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(
                "pgextest");
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        entityManager.getTransaction().begin();
        // here I attempt to persist an object with an ID that is already in use
        entityManager.persist(new PGExceptionTestBean(1));
        entityManager.getTransaction().commit();
        entityManager.close();
    }
}

src/main/resources/log4j.properties

log4j.rootLogger=ERROR, stdout

log4j.appender.stdout=org.Apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.Apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

src/main/resources/META-INF/persistence.xml

<persistence xmlns="http://Java.Sun.com/xml/ns/persistence"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://Java.Sun.com/xml/ns/persistence http://Java.Sun.com/xml/ns/persistence/persistence_2_0.xsd"
        version="2.0">
    <persistence-unit name="pgextest">
        <properties>
            <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost/pgextest"/>
            <property name="javax.persistence.jdbc.user" value="postgres"/>
            <property name="javax.persistence.jdbc.password" value="postgres"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
            <property name="hibernate.jdbc.batch_size" value="5"/>
        </properties>
    </persistence-unit>
</persistence>

pom.xml

<project xmlns="http://maven.Apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.Apache.org/POM/4.0.0 http://maven.Apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>pgextest</groupId>
    <artifactId>pgextest</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>3.6.9.Final</version>
        </dependency>   

        <dependency>
            <groupId>postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>9.1-901.jdbc4</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.1</version>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</project>

L'exécution de la méthode principale consignera ensuite les éléments suivants:

ERROR [main] - Batch entry 0 insert into PGExceptionTestBean (label, id) values (NULL, '1') was aborted.  Call getNextException to see the cause.
ERROR [main] - ERROR: duplicate key value violates unique constraint "pgexceptiontestbean_pkey"

Il est probablement utile de mentionner que vous pouvez désactiver le traitement par lots JDBC qui encapsule l'exception d'origine en définissant la propriété hibernate.jdbc.batch_size sur 0 (il va sans dire que vous ne voulez probablement pas le faire en production.)

7
ryanp

Cela a fonctionné pour moi pour obtenir le message d'exception qui a causé le problème (Hibernate 3.2.5.ga):

catch (JDBCException jdbce) {
    jdbce.getSQLException().getNextException().printStackTrace();
}
13
user3652083

Je pense que Aspect Programming est une meilleure solution pour résoudre ce genre de problème.

Toutefois, si vous souhaitez écrire un code personnalisé à cette fin, vous pouvez intercepter SqlException, le parcourir en boucle et enregistrer chaque exception. Quelque chose comme ça devrait marcher.

try {
 // whatever your code is
} catch (SQLException e) {
    while(e!= null) {
      logger.log(e);
      e = e.getNextException();
    }
}
4
Ankit Bansal

Pour moi, l'exception était une exception PersistenceException.

try {
//...
} catch (javax.persistence.PersistenceException e) {
    log.error(((Java.sql.BatchUpdateException) e.getCause().getCause()).getNextException());
}
4
Amalgovinus
try {
 // code
} catch (SQLException e) {      
  for (Throwable throwable : e) {
        log.error("{}", throwable);
  }
}
3
kayz1

Juste au cas où, si tel est le cas, vous obtenez cette exception dans un test JUnit, vous pouvez transformer l’exception JUnit avec une variable TestRule (inspirée de la source ExpectedExceptionTestRule)

public class HibernateBatchUnwindRule implements TestRule {

    private boolean handleAssumptionViolatedExceptions = false;

    private boolean handleAssertionErrors = false;

    private HibernateBatchUnwindRule() {
    }

    public static HibernateBatchUnwindRule create(){
        return new HibernateBatchUnwindRule();
    }

    public HibernateBatchUnwindRule handleAssertionErrors() {
        handleAssertionErrors = true;
        return this;
    }

    public HibernateBatchUnwindRule handleAssumptionViolatedExceptions() {
        handleAssumptionViolatedExceptions = true;
        return this;
    }

    public Statement apply(Statement base,
            org.junit.runner.Description description) {
        return new ExpectedExceptionStatement(base);
    }

    private class ExpectedExceptionStatement extends Statement {
        private final Statement fNext;

        public ExpectedExceptionStatement(Statement base) {
            fNext = base;
        }

        @Override
        public void evaluate() throws Throwable {
            try {
                fNext.evaluate();
            } catch (AssumptionViolatedException e) {
                optionallyHandleException(e, handleAssumptionViolatedExceptions);
            } catch (AssertionError e) {
                optionallyHandleException(e, handleAssertionErrors);
            } catch (Throwable e) {
                handleException(e);
            }
        }
    }

    private void optionallyHandleException(Throwable e, boolean handleException)
            throws Throwable {
        if (handleException) {
            handleException(e);
        } else {
            throw e;
        }
    }

    private void handleException(Throwable e) throws Throwable {
        Throwable cause = e.getCause();
        while (cause != null) {
            if (cause instanceof BatchUpdateException) {
                BatchUpdateException batchUpdateException = (BatchUpdateException) cause;
                throw batchUpdateException.getNextException();
            }
            cause = cause.getCause();
        };
        throw e; 
    }
}

puis ajoutez la règle au cas de test

public class SomeTest {

    @Rule
    public HibernateBatchUnwindRule batchUnwindRule = HibernateBatchUnwindRule.create();

    @Test
    public void testSomething(){...}
  }
1
Boris Treukhov

Si par hasard vous rencontrez cette exception de Kafka-Connect, vous pouvez définir la propriété batch.size sur 0 (temporairement) pour révéler l'exception rencontrée par votre opérateur de collecteur.

0
mancini0