I am using Java 1.8 and H2 1.4.199.
I have a method insertRecord(DATA_OBJECT) which inserts single row into table, and then returns the generated ID for this row. It instantiates a Task object and submit it onto SingleThreadExecutorService one-by-one, and the generated ID is retrieved from Future object. Most of the time it works just fine. However, sometimes a situation like this happens:
- Row 10 insert is submitted to ExecutorService
- Row 11 insert is meant to be waiting in front of "synchronized" block to be submitted to ExecutorService
- Row 10 is being inserted by code, and Future returns generated ID = 11
- Row 11 is being inserted by code, and Future returns generated ID = 11
I have no working and reproducible example, because it is a very rare situation, but it happens.
No exceptions thrown and I have no idea how this could happen.
Below is the example of code:
private static final ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
private static String insertRecord(DATA_OBJECT dataObject) {
Future<String> future;
synchronized (singleExecutor) {
future = singleExecutor.submit(new Task(dataObject));
}
try {
return future.get();
} catch (InterruptedException ex) {
Logger.getLogger(CLASS_NAME.class.getName()).log(Level.SEVERE, null, ex);
} catch (ExecutionException ex) {
Logger.getLogger(CLASS_NAME.class.getName()).log(Level.SEVERE, null, ex);
}
}
private class Task implements Callable<String> {
private DATA_OBJECT dataObject;
public Task(DATA_OBJECT dataObject) {
this.dataObject = dataObject;
}
@Override
public String call() {
try {
return execute(dataObject);
} catch (SQLException ex) {
Logger.getLogger(CLASS_NAME.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
}
private static String execute(DATA_OBJECT dataObject) {
Connection conn = Database.getTransactedConnection();
String lastId = null;
boolean success = false;
try (
PreparedStatement statement =
conn.prepareStatement(
"insert into TABLE (COLUMN_NAMES) values (VALUES)",
Statement.RETURN_GENERATED_KEYS
)
) {
statement.setString(1, dataObject.STRING_1);
statement.setString(N, dataObject.STRING_N);
success = statement.executeUpdate() == 1;
if (success) {
try (ResultSet generatedKeys = statement.getGeneratedKeys()) {
if (generatedKeys.next()) {
lastID = generatedKeys.getString(1);
}
}
}
} finally {
if (success)
conn.commit();
else
conn.rollback();
Database.releaseConnection(conn);
}
return lastId;
}
I am trying to find a way to avoid 100% of these events because the consequences are devastating. How can I do this?
What could be the problem leading to the return of the identifier from the future?