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.
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.)
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();
}
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();
}
}
Pour moi, l'exception était une exception PersistenceException.
try {
//...
} catch (javax.persistence.PersistenceException e) {
log.error(((Java.sql.BatchUpdateException) e.getCause().getCause()).getNextException());
}
try {
// code
} catch (SQLException e) {
for (Throwable throwable : e) {
log.error("{}", throwable);
}
}
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 ExpectedException
TestRule
)
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(){...}
}
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.