diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperFactory.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperFactory.java
index 03fdf7f..bd4ea2c 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperFactory.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperFactory.java
@@ -56,7 +56,7 @@ public class EntitySqlDaoWrapperFactory<InitialSqlDao extends EntitySqlDao> {
private <NewSqlDao extends EntitySqlDao<NewEntity>, NewEntity extends Entity> NewSqlDao create(final Class<NewSqlDao> newSqlDaoClass, final NewSqlDao newSqlDao) {
final ClassLoader classLoader = newSqlDao.getClass().getClassLoader();
final Class[] interfacesToImplement = {newSqlDaoClass};
- final EntitySqlDaoWrapperInvocationHandler<NewSqlDao, NewEntity> wrapperInvocationHandler = new EntitySqlDaoWrapperInvocationHandler<NewSqlDao, NewEntity>(newSqlDao);
+ final EntitySqlDaoWrapperInvocationHandler<NewSqlDao, NewEntity> wrapperInvocationHandler = new EntitySqlDaoWrapperInvocationHandler<NewSqlDao, NewEntity>(newSqlDaoClass, newSqlDao);
final Object newSqlDaoObject = Proxy.newProxyInstance(classLoader, interfacesToImplement, wrapperInvocationHandler);
return newSqlDaoClass.cast(newSqlDaoObject);
diff --git a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
index eda110f..fbf6a1c 100644
--- a/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
+++ b/util/src/main/java/com/ning/billing/util/entity/dao/EntitySqlDaoWrapperInvocationHandler.java
@@ -19,12 +19,16 @@ package com.ning.billing.util.entity.dao;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.exceptions.DBIException;
+import org.skife.jdbi.v2.exceptions.StatementException;
import org.skife.jdbi.v2.sqlobject.Bind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,9 +53,11 @@ public class EntitySqlDaoWrapperInvocationHandler<T extends EntitySqlDao<U>, U e
private final Logger logger = LoggerFactory.getLogger(EntitySqlDaoWrapperInvocationHandler.class);
+ private final Class<T> sqlDaoClass;
private final T sqlDao;
- public EntitySqlDaoWrapperInvocationHandler(final T sqlDao) {
+ public EntitySqlDaoWrapperInvocationHandler(final Class<T> sqlDaoClass, final T sqlDao) {
+ this.sqlDaoClass = sqlDaoClass;
this.sqlDao = sqlDao;
}
@@ -60,16 +66,25 @@ public class EntitySqlDaoWrapperInvocationHandler<T extends EntitySqlDao<U>, U e
try {
return invokeSafely(proxy, method, args);
} catch (Throwable t) {
- logger.warn("Error during transaction for entity {} and method {}", sqlDao.getClass(), method.getName());
-
if (t.getCause() != null && t.getCause().getCause() != null && DBIException.class.isAssignableFrom(t.getCause().getClass())) {
// Likely the JDBC exception or a Billing exception we have thrown in the transaction
- errorDuringTransaction(t.getCause().getCause());
+ // If it's a JDBC error, try to extract the SQL statement
+ if (t.getCause() instanceof StatementException) {
+ final StatementContext statementContext = ((StatementException) t.getCause()).getStatementContext();
+ if (statementContext != null) {
+ final PreparedStatement statement = statementContext.getStatement();
+ // Note: we rely on the JDBC driver to have a sane toString() method...
+ errorDuringTransaction(t.getCause().getCause(), method, statement.toString());
+ // Never reached
+ return null;
+ }
+ }
+ errorDuringTransaction(t.getCause().getCause(), method);
} else if (t.getCause() != null) {
// t is likely not interesting (java.lang.reflect.InvocationTargetException)
- errorDuringTransaction(t.getCause());
+ errorDuringTransaction(t.getCause(), method);
} else {
- errorDuringTransaction(t);
+ errorDuringTransaction(t, method);
}
}
@@ -78,7 +93,22 @@ public class EntitySqlDaoWrapperInvocationHandler<T extends EntitySqlDao<U>, U e
}
// Nice method name to ease debugging while looking at log files
- private void errorDuringTransaction(final Throwable t) throws Throwable {
+ private void errorDuringTransaction(final Throwable t, final Method method, final String extraErrorMessage) throws Throwable {
+ final StringBuilder errorMessageBuilder = new StringBuilder("Error during transaction for sql entity {} and method {}");
+ if (t instanceof SQLException) {
+ final SQLException sqlException = (SQLException) t;
+ errorMessageBuilder.append(" [SQL State: ")
+ .append(sqlException.getSQLState())
+ .append(", Vendor Error Code: ")
+ .append(sqlException.getErrorCode())
+ .append("]");
+ }
+ if (extraErrorMessage != null) {
+ // This is usually the SQL statement
+ errorMessageBuilder.append("\n").append(extraErrorMessage);
+ }
+ logger.warn(errorMessageBuilder.toString(), sqlDaoClass, method.getName());
+
// This is to avoid throwing an exception wrapped in an UndeclaredThrowableException
if (!(t instanceof RuntimeException)) {
throw new RuntimeException(t);
@@ -87,6 +117,10 @@ public class EntitySqlDaoWrapperInvocationHandler<T extends EntitySqlDao<U>, U e
}
}
+ private void errorDuringTransaction(final Throwable t, final Method method) throws Throwable {
+ errorDuringTransaction(t, method, null);
+ }
+
private Object invokeSafely(final Object proxy, final Method method, final Object[] args) throws Throwable {
final Audited annotation = method.getAnnotation(Audited.class);