-
Notifications
You must be signed in to change notification settings - Fork 38.6k
Description
Affects: 3.0.0.M1 to 5.2.0.RELEASE
Goal
I'm trying to use the jdbcTemplate.batchUpdate and would like to know the exact failed statement.
Issues
- Trying to catch the BatchUpdateException which contains the counters does not work because the BatchUpdateException gets removed from the exception stack.
- Even when the BatchUpdateException gets returned it only contains the counters of the current batch. The successful batch counts need to be communicated back to the caller as well.
Code to reproduce the problem
I use this table to quickly get a unique constraint exception
create table batch_test (
id number primary key
);
Configuration
Spring: 5.1.3.RELEASE
Database: Oracle 18c
Code trying to catch the BatchUpdateException
final List<Integer> primaryKeys = Arrays.asList(1, 2, 3, 4, 5, 6, 2, 7, 8, 9);
try {
return jdbcTemplate.batchUpdate(
"insert into batch_test values (?)",
primaryKeys,
4,
(ps, primaryKey) -> {
ps.setInt(1, primaryKey);
}
);
} catch (final DataAccessException e) {
final Throwable cause = e.getCause();
if (cause instanceof BatchUpdateException) {
final BatchUpdateException batchUpdateException = (BatchUpdateException) cause;
final long[] updateCounts = batchUpdateException.getLargeUpdateCounts();
// log the exact one that failed
for (int index = 0; index < updateCounts.length; index++) {
if (updateCounts[index] == Statement.EXECUTE_FAILED) {
logger.debug("The insert of element " + index + " of the array failed");
}
}
if (updateCounts.length < primaryKeys.size()) {
logger.debug("The insert of elements " + (updateCounts.length - 1)
+ " to " + (primaryKeys.size() - 1) + " of the array failed");
}
}
throw e;
}
Possible solutions
The problem I'm having happens here:
https://github.com/spring-projects/spring-framework/blame/master/spring-jdbc/src/main/java/org/springframework/jdbc/support/SQLErrorCodeSQLExceptionTranslator.java#L179
The original stack sqlEx
gets overridden with the next exception and thus the information of the BatchUpdateException gets lost.
I understand the intent to translate the cause into an exception that is a little more meaningful, but IMHO the original BatchUpdateException should not be trashed.
Wherever a new exception is created the original exception should be used for the stack. So instead of:
DataAccessException dae = customTranslate(task, sql, sqlEx);
DataAccessException customDex = customTranslator.translate(task, sql, sqlEx);
DataAccessException customException = createCustomException(task, sql, sqlEx, customTranslation.getExceptionClass());
return new BadSqlGrammarException(task, (sql != null ? sql : ""), sqlEx);
the original exception should be used:
DataAccessException dae = customTranslate(task, sql, ex);
DataAccessException customDex = customTranslator.translate(task, sql, ex);
DataAccessException customException = createCustomException(task, sql, ex, customTranslation.getExceptionClass());
return new BadSqlGrammarException(task, (sql != null ? sql : ""), ex);
The second issue is probably here:
https://github.com/spring-projects/spring-framework/blob/master/spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java#L1059
rowsAffected
needs to be communicated back to the caller to pinpoint the problematic update
Workaround
My current solution consist of writing my own Translator, overriding the doTranslate method and setting the translator when I create the jdbcTemplate Bean
public class BatchUpdateExceptionTranslator extends SQLErrorCodeSQLExceptionTranslator {
@Override
@Nullable
protected DataAccessException doTranslate(String task, @Nullable String sql, SQLException ex) {
// exchanging sqlEx with ex whenever creating a new Exception
}
}
@Configuration
public class MyConfiguration {
@Bean
public JdbcTemplate jdbcTemplate(
final DataSource dataSource
) {
final JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setExceptionTranslator(new BatchUpdateExceptionTranslator());
return jdbcTemplate;
}
}
The second issue cannot be fixed as easily. Either avoid the use of the batched update or set the batch size very high to not run into that problem