/** * Initializes a {@link JdbcEventStorageEngine} as specified through this Builder. * * @return a {@link JdbcEventStorageEngine} as specified through this Builder */ public JdbcEventStorageEngine build() { return new JdbcEventStorageEngine(this); }
@Override protected void storeSnapshot(DomainEventMessage<?> snapshot, Serializer serializer) { transactionManager.executeInTransaction(() -> { try { executeUpdates( getConnection(), e -> handlePersistenceException(e, snapshot), connection -> appendSnapshot(connection, snapshot, serializer), connection -> deleteSnapshots( connection, snapshot.getAggregateIdentifier(), snapshot.getSequenceNumber() ) ); } catch (ConcurrencyException e) { // Ignore duplicate key issues in snapshot. It just means a snapshot already exists } }); }
@ConditionalOnMissingBean({EventStorageEngine.class, EventStore.class}) @Bean public EventStorageEngine eventStorageEngine(Serializer defaultSerializer, PersistenceExceptionResolver persistenceExceptionResolver, @Qualifier("eventSerializer") Serializer eventSerializer, AxonConfiguration configuration, ConnectionProvider connectionProvider, TransactionManager transactionManager) { return JdbcEventStorageEngine.builder() .snapshotSerializer(defaultSerializer) .upcasterChain(configuration.upcasterChain()) .persistenceExceptionResolver(persistenceExceptionResolver) .eventSerializer(eventSerializer) .connectionProvider(connectionProvider) .transactionManager(transactionManager) .build(); }
private List<TrackedEventData<?>> executeEventDataQuery(GapAwareTrackingToken cleanedToken, int batchSize) { return executeQuery( getConnection(), connection -> readEventData(connection, cleanedToken, batchSize), resultSet -> { GapAwareTrackingToken previousToken = cleanedToken; List<TrackedEventData<?>> results = new ArrayList<>(); while (resultSet.next()) { TrackedEventData<?> next = getTrackedEventData(resultSet, previousToken); results.add(next); previousToken = (GapAwareTrackingToken) next.trackingToken(); } return results; }, e -> new EventStoreException(format("Failed to read events from token [%s]", cleanedToken), e) ); }
@Override protected Stream<? extends DomainEventData<?>> readSnapshotData(String aggregateIdentifier) { return transactionManager.fetchInTransaction(() -> { List<DomainEventData<?>> result = executeQuery(getConnection(), connection -> readSnapshotData(connection, aggregateIdentifier), JdbcUtils.listResults(this::getSnapshotData), e -> new EventStoreException( format("Error reading aggregate snapshot [%s]", aggregateIdentifier), e)); return result.stream(); }); }
executeBatch(getConnection(), connection -> { PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setLong(3, event.getSequenceNumber()); preparedStatement.setString(4, event.getType()); writeTimestamp(preparedStatement, 5, event.getTimestamp()); preparedStatement.setString(6, payload.getType().getName()); preparedStatement.setString(7, payload.getType().getRevision()); }, e -> handlePersistenceException(e, events.get(0))));
private GapAwareTrackingToken cleanGaps(TrackingToken lastToken) { SortedSet<Long> gaps = ((GapAwareTrackingToken) lastToken).getGaps(); return executeQuery(getConnection(), conn -> { PreparedStatement statement = conn.prepareStatement(format("SELECT %s, %s FROM %s WHERE %s >= ? AND %s <= ?", long sequenceNumber = resultSet.getLong(schema.globalIndexColumn()); Instant timestamp = DateTimeUtils.parseInstant(readTimeStamp(resultSet, schema.timestampColumn()).toString()); if (gaps.contains(sequenceNumber) || timestamp.isAfter(gapTimeoutFrame())) {
cleanedToken = cleanGaps(lastToken); } else { cleanedToken = (GapAwareTrackingToken) lastToken; List<TrackedEventData<?>> eventData = executeEventDataQuery(cleanedToken, batchSize); Long result = executeQuery(getConnection(), connection -> { PreparedStatement stat = connection.prepareStatement( e)); if (result != null) { return executeEventDataQuery(cleanedToken, (int) (result - index));
resultSet.getLong(schema.sequenceNumberColumn()), resultSet.getString(schema.eventIdentifierColumn()), readTimeStamp(resultSet, schema.timestampColumn()), resultSet.getString(schema.payloadTypeColumn()), resultSet.getString(schema.payloadRevisionColumn()), readPayload(resultSet, schema.payloadColumn()), readPayload(resultSet, schema.metaDataColumn()) ); boolean allowGaps = domainEvent.getTimestamp().isAfter(gapTimeoutFrame()); GapAwareTrackingToken token = previousToken; if (token == null) {
@Override public TrackingToken createTailToken() { String sql = "SELECT min(" + schema.globalIndexColumn() + ") - 1 FROM " + schema.domainEventTable(); Long index = transactionManager.fetchInTransaction( () -> executeQuery(getConnection(), connection -> connection.prepareStatement(sql), resultSet -> nextAndExtract(resultSet, 1, Long.class), e -> new EventStoreException("Failed to get tail token", e))); return Optional.ofNullable(index) .map(seq -> GapAwareTrackingToken.newInstance(seq, Collections.emptySet())) .orElse(null); }
@Override protected List<? extends DomainEventData<?>> fetchDomainEvents(String aggregateIdentifier, long firstSequenceNumber, int batchSize) { return transactionManager.fetchInTransaction( () -> executeQuery( getConnection(), connection -> readEventData(connection, aggregateIdentifier, firstSequenceNumber, batchSize), JdbcUtils.listResults(this::getDomainEventData), e -> new EventStoreException( format("Failed to read events for aggregate [%s]", aggregateIdentifier), e ) )); }
/** * Returns a comma separated list of tracked domain event column names to select from an event entry. * * @return comma separated tracked domain event column names. */ protected String trackedEventFields() { return schema.globalIndexColumn() + ", " + domainEventFields(); }
private List<TrackedEventData<?>> executeEventDataQuery(GapAwareTrackingToken cleanedToken, int batchSize) { return executeQuery( getConnection(), connection -> readEventData(connection, cleanedToken, batchSize), resultSet -> { GapAwareTrackingToken previousToken = cleanedToken; List<TrackedEventData<?>> results = new ArrayList<>(); while (resultSet.next()) { TrackedEventData<?> next = getTrackedEventData(resultSet, previousToken); results.add(next); previousToken = (GapAwareTrackingToken) next.trackingToken(); } return results; }, e -> new EventStoreException(format("Failed to read events from token [%s]", cleanedToken), e) ); }
executeBatch(getConnection(), connection -> { PreparedStatement preparedStatement = connection.prepareStatement(sql); preparedStatement.setLong(3, event.getSequenceNumber()); preparedStatement.setString(4, event.getType()); writeTimestamp(preparedStatement, 5, event.getTimestamp()); preparedStatement.setString(6, payload.getType().getName()); preparedStatement.setString(7, payload.getType().getRevision()); }, e -> handlePersistenceException(e, events.get(0))));
private GapAwareTrackingToken cleanGaps(TrackingToken lastToken) { SortedSet<Long> gaps = ((GapAwareTrackingToken) lastToken).getGaps(); return executeQuery(getConnection(), conn -> { PreparedStatement statement = conn.prepareStatement(format("SELECT %s, %s FROM %s WHERE %s >= ? AND %s <= ?", long sequenceNumber = resultSet.getLong(schema.globalIndexColumn()); Instant timestamp = DateTimeUtils.parseInstant(readTimeStamp(resultSet, schema.timestampColumn()).toString()); if (gaps.contains(sequenceNumber) || timestamp.isAfter(gapTimeoutFrame())) {
cleanedToken = cleanGaps(lastToken); } else { cleanedToken = (GapAwareTrackingToken) lastToken; List<TrackedEventData<?>> eventData = executeEventDataQuery(cleanedToken, batchSize); Long result = executeQuery(getConnection(), connection -> { PreparedStatement stat = connection.prepareStatement( e -> new EventStoreException("Failed to read globalIndex ahead of token", e)); if (result != null) { return executeEventDataQuery(cleanedToken, (int) (result - index));
resultSet.getLong(schema.sequenceNumberColumn()), resultSet.getString(schema.eventIdentifierColumn()), readTimeStamp(resultSet, schema.timestampColumn()), resultSet.getString(schema.payloadTypeColumn()), resultSet.getString(schema.payloadRevisionColumn()), readPayload(resultSet, schema.payloadColumn()), readPayload(resultSet, schema.metaDataColumn()) ); boolean allowGaps = domainEvent.getTimestamp().isAfter(gapTimeoutFrame()); GapAwareTrackingToken token = previousToken; if (token == null) {
@Override public TrackingToken createHeadToken() { String sql = "SELECT max(" + schema.globalIndexColumn() + ") FROM " + schema.domainEventTable(); Long index = transactionManager.fetchInTransaction( () -> executeQuery(getConnection(), connection -> connection.prepareStatement(sql), resultSet -> nextAndExtract(resultSet, 1, Long.class), e -> new EventStoreException("Failed to get head token", e))); return Optional.ofNullable(index) .map(seq -> GapAwareTrackingToken.newInstance(seq, Collections.emptySet())) .orElse(null); }
@Override protected Stream<? extends DomainEventData<?>> readSnapshotData(String aggregateIdentifier) { return transactionManager.fetchInTransaction(() -> { List<DomainEventData<?>> result = executeQuery(getConnection(), connection -> readSnapshotData(connection, aggregateIdentifier), JdbcUtils.listResults(this::getSnapshotData), e -> new EventStoreException( format("Error reading aggregate snapshot [%s]", aggregateIdentifier), e)); return result.stream(); }); }
@Override protected List<? extends DomainEventData<?>> fetchDomainEvents(String aggregateIdentifier, long firstSequenceNumber, int batchSize) { return transactionManager.fetchInTransaction( () -> executeQuery( getConnection(), connection -> readEventData(connection, aggregateIdentifier, firstSequenceNumber, batchSize), JdbcUtils.listResults(this::getDomainEventData), e -> new EventStoreException( format("Failed to read events for aggregate [%s]", aggregateIdentifier), e ) )); }