Ich beschreibe eine Möglichkeit Oracle Sequences in Spring Data JDBC zu integrieren. Zum Einsatz kommt dabei Spring Data JDBC 2.0. Von Haus aus unterstützt diese Version keine Sequences.

Problem

Spring Data JDBC ermöglicht die Anbindung einer Datenbank ohne Umwege über JPA. Es setzt außerdem Best Practices aus dem Domain Driven Design um. Damit ist Spring Data JDBC durchaus eine ernstzunehmende Alternative zu Spring Data JPA.

Leider unterstützt Spring Data JDBC in Version 2.0 nur Auto-Increment Columns für generierte IDs. Oracle Datenbanken setzen hingegen für gewöhnlich Sequences ein.

Lösung

Spring Data JDBC unterstützt sogenannte EntityCallbacks. Ich nutze das BeforeConvertCallback, um vor dem Speichern des Aggregats bei Bedarf eine ID aus der Sequence zu selektieren.

Beispiel

Zunächst lege ich den BeforeConvertCallback an. Ich markiere ihn als @Component und lasse mir das JdbcTemplate injizieren. Dadurch wird der Lebenszyklus des Callbacks von Spring Data JDBC verwaltet.

BeforeConvertCallback wird zusätzlich über ein Generic typisiert. In meinem Fall ist es die Beispielklasse DemoAggregate.

import org.springframework.data.relational.core.mapping.event.BeforeConvertCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class SelectSequenceBeforeConvertCallback implements BeforeConvertCallback<DemoAggregate> {

    private final JdbcTemplate jdbcTemplate;

    public SelectSequenceBeforeConvertCallback(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

}

Das Interface BeforeConvertCallback erfordert die Implementierung der Methode onBeforeConvert(T). Die Methode wird beim Anlegen, Aktualisieren und Löschen eines Aggregats aufgerufen. Ich prüfe daher zunächst, ob noch keine ID (der Primärschlüssel) gesetzt ist. Es handelt sich in diesem Fall um ein neues Aggregat. Für sie selektiere ich über das JdbcTemplate eine neue ID aus der Sequence und weise sie zu.

@Override
public DemoAggregate onBeforeConvert(DemoAggregate demoAggregate) {
    if (demoAggregate.getId() == null) {
        Long id = jdbcTemplate.query("SELECT DEMO_SEQUENCE.NEXTVAL FROM DUAL", resultSet -> {
            if (resultSet.next()) {
                return resultSet.getLong(1);
            } else {
                throw new SQLException("Fehler beim Abrufen der Sequence");
            }
        });
        demoAggregate.setId(id);
    }
    return demoAggregate;
}

Erweiterungsmöglichkeiten

Im beschriebenen Fall behandelt der skizzierte SelectSequenceBeforeConvertCallback nur ein einziges Aggregate. Wenn der Callback für mehrere Aggregate verwendet werden soll, muss für jedes Aggregat ein eigener BeforeConvertCallback definiert werden, der die entsprechende Sequence abfragt. Hier hilft es, die gemeinsam genutzte Funktionalität in eine abstrakte Oberklasse auszulagern und in konkreten Unterklassen die notwendige Konfiguration vorzunehmen.