Hi everyone,
I think there is an inconsistency between the JdbcEventStorageEngine and the InMemoryStorageEngine when it comes to the lastSequenceNumberFor() method. The InMemoryStorageEngine version returns an empty Optional when an aggregate is not found while the JdbcEventStorageEngine returns an Optional with the value of 0 for the same condition.
I believe the issue here may be caused by the following two things:
-
The query used in the aforementioned method returns a single row with a NULL value when executed when the aggregate is not found.
SELECT max(sequence_number) FROM domain_event_entry WHERE aggregate_identifier = ‘XYZ’
This leads to the following issue.
-
The ResultSet getObject() method returns 0 instead of a NULL value
resultSet.getObject(1, Long.class);
This is because, internally MySQL (through the ResultSetImpl implementation), uses the ResultSet getLong() method which returns 0 if the SQL column read yielded a NULL value.
} else if (type.equals(Long.class) || type.equals(Long.TYPE)) {
return (T) Long.valueOf(getLong(columnIndex));
In order to determine whether the value read from the ResultSet was a NULL or not I usually rely on the ResultSet wasNull() method.
Following is my copy of the lastSequenceNumberFor() which works as expected.
@Override
public Optional lastSequenceNumberFor(final String aggregateIdentifier) {
final String sql = “SELECT max(” + schema().sequenceNumberColumn() + “) FROM " + schema().domainEventTable() + " WHERE " + schema().aggregateIdentifierColumn() + " = ?”;
return Optional.ofNullable(transactionManager.fetchInTransaction(
() -> JdbcUtils.executeQuery(getConnection(), connection -> {
final PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, aggregateIdentifier);
return stmt;
}, resultSet -> {
if (resultSet.next()) {
final Long value = resultSet.getObject(1, Long.class);
if (false == resultSet.wasNull()) {
return value;
}
}
return null;
},
e -> new EventStoreException(String.format(“Failed to read events for aggregate [%s]”, aggregateIdentifier), e))));
}
Basically, I replaced the statement
resultSet -> resultSet.next() ? resultSet.getObject(1, Long.class) : null
with
resultSet -> {
if (resultSet.next()) {
final Long value = resultSet.getObject(1, Long.class);
if (false == resultSet.wasNull()) {
return value;
}
}
return null;
}
as highlighted in the above example.
Any feedback is highly appreciated.
Albert