Modul 05 · Migration Deep Dive · Stufe 4 Daten und Transaktionen

Transaktionen

manuelles Commit/Rollback wird nachvollziehbar in @Transactional überführt. Die Akte zeigt nicht nur das Ziel, sondern die tatsächlichen Dateien, APIs, Dependencies, Codebelege, Tests, Risiken und Cutover-Schritte.

Transaction ScriptService Layer
2 → 3Produktionsdateien
84 → 56Java-Zeilen
1 → 1Testdateien
1 / 2Dependencies entfernt / neu
NIEDRIGRisiko · Score 0

Was sich konkret ändert

DimensionLegacyModernMigrationskonsequenz
Programmiermodellimperativ / ohne Framework-AnnotationenRepository, Service, SpringBootApplication, TransactionalAnnotationen und Containerfunktionen werden nur dort eingesetzt, wo sie eine konkrete technische Verantwortung übernehmen.
Abhängigkeiten2 direkte Dependencies3 direkte Dependencies2 neu, 1 entfernt; Versionen und transitive Auswirkungen im erfolgreichen Online-Build prüfen.
Öffentliche API3 erkannte Methoden4 erkannte MethodenMethoden werden nach fachlicher Rolle gemappt; reine Bootstrap- und Framework-Methoden sind kein fachlicher Vertrag.
Datenmodellclass LegacyTransferService, class TransactionsLegacyApplicationclass AccountRepository, class TransactionsModernApplication, class TransferServiceFeldnamen, IDs, Null-Semantik, Gleichheit und Serialisierungsform werden separat regressionstestet.
FehlerverhaltenLegacy-Exceptions und Rückgabewerteexplizitere Fach-/Framework-FehlerabbildungFehler dürfen nicht nur technisch übersetzt werden; Status, Ursache, Retrybarkeit und Client-Vertrag müssen erhalten oder versioniert werden.
TestsBestands- und Golden-Master-TestsUnit-, Slice-, Contract- und IntegrationstestsDer Modern-Pfad wird zuerst gegen denselben fachlichen Vektor geprüft und danach um neue technische Risiken ergänzt.
BetriebLegacy-Start/Lifecyclemodernes Packaging, Health und externe KonfigurationMigration mit produktionsnahen Daten, Backups, Restore-Probe und Query-/Lock-Monitoring absichern.
RollbackLegacy-Artefakt bleibt unverändertModern-Artefakt getrennt deploybarKein Rollback über Datenverlust: Schema, Nachrichten und verschlüsselte Daten müssen rückwärtslesbar oder durch Dual-Read abgesichert sein.

Fachlicher Vertrag: unverändert zu erhalten

  • Eine Überweisung ist atomar: beide Konten oder keines werden geändert.
  • Rollback-Regeln für fachliche und technische Exceptions sind explizit.
  • Beträge werden ohne Gleitkommafehler verarbeitet.

Hauptrisiko und Testfokus

partial writes and incorrect rollback rules

success, checked/unchecked failure and rollback assertions

Die Modernisierung gilt erst als abgeschlossen, wenn der fachliche Vertrag automatisiert belegt ist.

Verbindliches Code- und Rollenmapping

Die Zuordnung ist semantisch: Eine Legacy-Klasse kann in mehrere moderne Rollen zerlegt werden.

Legacy-Rolle / DateiModern-Rolle / DateiBedeutung
LegacyTransferService.javaTransferService.java + AccountRepository.javaManuelle Connection-Transaktion wird deklarativer Service plus Datenadapter.
TransactionsLegacyApplication.javaTransactionsModernApplication.javaBootstrap wechselt auf Spring Transaction Management.

Legacy-Quellinventar

DateiTypAnnotationenöffentliche APIPatternZeilen
LegacyTransferService.javaclass LegacyTransferServicevoid transfer(long from, long to, BigDecimal amount, boolean simulateFailure)
BigDecimal balance(long id)
Transaction Script69
TransactionsLegacyApplication.javaclass TransactionsLegacyApplicationvoid main(String[] args)15

Modern-Quellinventar

DateiTypAnnotationenöffentliche APIPatternZeilen
AccountRepository.javaclass AccountRepositoryRepositoryvoid changeBalance(long id, BigDecimal delta)
BigDecimal balance(long id)
23
TransactionsModernApplication.javaclass TransactionsModernApplicationSpringBootApplicationvoid main(String[] args)11
TransferService.javaclass TransferServiceService, Transactionalvoid transfer(long from, long to, BigDecimal amount, boolean simulateFailure)Service Layer + declarative Transaction Boundary22

Dependency- und Laufzeitmigration

StatusDependencyVersionScopePrüfung
ENTFERNTorg.junit.jupiter:junit-jupiterBOM/ParenttestLegacy-Laufzeit entfällt oder wird durch Plattform/BOM ersetzt.
NEUorg.springframework.boot:spring-boot-starter-jdbcBOM/ParentcompileNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
NEUorg.springframework.boot:spring-boot-starter-testBOM/ParenttestNeue Laufzeit-/Testabhängigkeit; transitiv, lizenz- und security-seitig prüfen.
GEÄNDERTcom.h2database:h2BOM/Parent → BOM/Parentcompile → runtimeGemeinsame Dependency; Version und Scope im effektiven POM prüfen.
Direkte POM-Daten ersetzen keinen erfolgreichen Online-Build mit transitiver SBOM- und CVE-Prüfung.

Vorher-/Nachher-Codebelege

Die folgenden Ausschnitte stammen direkt aus den enthaltenen Projekten. Dadurch ist sichtbar, welche Verantwortung tatsächlich verschoben wurde.

Legacy
LegacyTransferService.java
Modern
TransferService.java + AccountRepository.java

Migrationsbedeutung: Manuelle Connection-Transaktion wird deklarativer Service plus Datenadapter.

Legacy-Code

projects/05-transactions/legacy/src/main/java/at/aydin/lab/transactions/legacy/LegacyTransferService.java

package at.aydin.lab.transactions.legacy;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

// Design Pattern: Transaction Script
// Zweck: Ein kompletter fachlicher Ablauf wird explizit in einer Methode transaktional ausgeführt.
public final class LegacyTransferService {
    private final String url;
    public LegacyTransferService(String url) {
        this.url = url;
        initialize();
    }

    private Connection connection() throws SQLException {
        return DriverManager.getConnection(url, "sa", "");
    }

    private void initialize() {
        try (Connection c = connection(); Statement s = c.createStatement()) {
            s.executeUpdate("create table if not exists account(id bigint primary key, balance decimal(15,2))");
            s.executeUpdate("merge into account key(id) values (1,1000.00),(2,200.00)");
        } catch (SQLException e) {
            throw new IllegalStateException(e);
        }
    }

    public void transfer(long from, long to, BigDecimal amount, boolean simulateFailure) {
        try (Connection c = connection()) {
            c.setAutoCommit(false);
            try {
                update(c, from, amount.negate());
                if (simulateFailure) throw new IllegalStateException("Simulierter Fehler");
                update(c, to, amount);
                c.commit();
            } catch (Exception e) {
                c.rollback();
                throw new IllegalStateException("Überweisung zurückgerollt", e);
            }
        } catch (SQLException e) {
            throw new IllegalStateException(e);
        }
    }

    private void update(Connection c, long id, BigDecimal delta) throws SQLException {
        try (PreparedStatement ps = c.prepareStatement("update account set balance=balance+? where id=?")) {
            ps.setBigDecimal(1, delta);
            ps.setLong(2, id);
            if (ps.executeUpdate() != 1) throw new SQLException("Konto fehlt: " + id);
        }
    }

    public BigDecimal balance(long id) {
        try (Connection c = connection(); PreparedStatement ps = c.prepareStatement("select balance from account where id=?")) {
            ps.setLong(1, id);
            try (ResultSet rs = ps.executeQuery()) {
                if (!rs.next()) throw new IllegalArgumentException();
                return rs.getBigDecimal(1);
            }
        } catch (SQLException e) {
            throw new IllegalStateException(e);
        }
    }
}

Modern-Code

projects/05-transactions/modern/src/main/java/at/aydin/lab/transactions/modern/TransferService.java

package at.aydin.lab.transactions.modern;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;

@Service
// Design Pattern: Service Layer + declarative Transaction Boundary
// Zweck: Rollback-Regeln werden vom Framework statt durch manuelles JDBC gesteuert.
public class TransferService {
    private final AccountRepository repository;
    public TransferService(AccountRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public void transfer(long from, long to, BigDecimal amount, boolean simulateFailure) {
        repository.changeBalance(from, amount.negate());
        if (simulateFailure) throw new IllegalStateException("Simulierter Fehler");
        repository.changeBalance(to, amount);
    }
}

Modern-Code

projects/05-transactions/modern/src/main/java/at/aydin/lab/transactions/modern/AccountRepository.java

package at.aydin.lab.transactions.modern;

import org.springframework.jdbc.core.simple.JdbcClient;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;

@Repository
public class AccountRepository {
    private final JdbcClient jdbc;
    public AccountRepository(JdbcClient jdbc) {
        this.jdbc = jdbc;
    }

    public void changeBalance(long id, BigDecimal delta) {
        int count = jdbc.sql("update account set balance=balance+:delta where id=:id").param("delta", delta).param("id", id)
                .update();
        if (count != 1) throw new IllegalArgumentException("Konto fehlt: " + id);
    }

    public BigDecimal balance(long id) {
        return jdbc.sql("select balance from account where id=:id").param("id", id).query(BigDecimal.class).single();
    }
}
Legacy
TransactionsLegacyApplication.java
Modern
TransactionsModernApplication.java

Migrationsbedeutung: Bootstrap wechselt auf Spring Transaction Management.

Legacy-Code

projects/05-transactions/legacy/src/main/java/at/aydin/lab/transactions/legacy/TransactionsLegacyApplication.java

package at.aydin.lab.transactions.legacy;

import java.math.BigDecimal;

public final class TransactionsLegacyApplication {
    private TransactionsLegacyApplication() {
    }

    public static void main(String[] args) {
        LegacyTransferService service = new LegacyTransferService("jdbc:h2:mem:txlegacy;DB_CLOSE_DELAY=-1");
        service.transfer(1, 2, new BigDecimal("50.00"), false);
        System.out.println("Konto 1: " + service.balance(1));
        System.out.println("Konto 2: " + service.balance(2));
    }
}

Modern-Code

projects/05-transactions/modern/src/main/java/at/aydin/lab/transactions/modern/TransactionsModernApplication.java

package at.aydin.lab.transactions.modern;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class TransactionsModernApplication {
    public static void main(String[] args) {
        SpringApplication.run(TransactionsModernApplication.class, args);
    }
}
Technischen Unified Diff öffnen
--- TransactionsLegacyApplication.java
+++ TransactionsModernApplication.java
@@ -1,15 +1,11 @@
-package at.aydin.lab.transactions.legacy;
+package at.aydin.lab.transactions.modern;
 
-import java.math.BigDecimal;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
 
-public final class TransactionsLegacyApplication {
-    private TransactionsLegacyApplication() {
-    }
-
+@SpringBootApplication
+public class TransactionsModernApplication {
     public static void main(String[] args) {
-        LegacyTransferService service = new LegacyTransferService("jdbc:h2:mem:txlegacy;DB_CLOSE_DELAY=-1");
-        service.transfer(1, 2, new BigDecimal("50.00"), false);
-        System.out.println("Konto 1: " + service.balance(1));
-        System.out.println("Konto 2: " + service.balance(2));
+        SpringApplication.run(TransactionsModernApplication.class, args);
     }
 }

Umsetzungsplan mit Qualitäts-Gates

  1. Arbeitspaket 1
    Commit-/Rollback-Pfade des LegacyTransferService mit Datenbanktests festhalten. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  2. Arbeitspaket 2
    AccountRepository als reinen Datenadapter ohne Transaktionssteuerung gestalten. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  3. Arbeitspaket 3
    TransferService als einzige Transaktionsgrenze definieren. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  4. Arbeitspaket 4
    Checked- und Runtime-Exception-Regeln explizit konfigurieren und testen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  5. Arbeitspaket 5
    Nebenläufige Transfers mit geeigneter Sperr-/Versionsstrategie prüfen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.
  6. Arbeitspaket 6
    Manuelle Connection-Steuerung erst nach Recovery- und Timeout-Tests entfernen. Nachweis: Commit/PR, automatisierter Test und aktualisierte Betriebsdokumentation.

Konkreter Test- und Abnahmekatalog

IDEbenePrüfungerforderlicher Nachweis
05-TRANSACTIONS-A01Integration/ContractSaldo-Summe bleibt vor und nach Transfer gleich.Automatisierter Test und CI-Protokoll
05-TRANSACTIONS-A02Integration/ContractFehler nach erster Buchung führt zum vollständigen Rollback.Automatisierter Test und CI-Protokoll
05-TRANSACTIONS-A03Integration/ContractUnzureichende Deckung erzeugt definierte Fachfehler.Automatisierter Test und CI-Protokoll
05-TRANSACTIONS-A04Integration/ContractParalleltests zeigen keine Lost Updates.Automatisierter Test und CI-Protokoll
05-TRANSACTIONS-A05Integration/ContractTransaktions-Timeout und Isolation sind dokumentiert.Automatisierter Test und CI-Protokoll
05-TRANSACTIONS-T01UnitFachlogik ohne Container oder externen Dienst testen.Unit-Test
05-TRANSACTIONS-T02RegressionLegacy- und Modern-Ergebnis für denselben Golden-Master-Vektor vergleichen.Vergleichsreport
05-TRANSACTIONS-T03NegativeFehlerhafte, leere und grenzwertige Eingaben prüfen.Negativtest
05-TRANSACTIONS-T04OperationsStart, Health, Shutdown und Konfigurationsfehler prüfen.Deployment-/Startprotokoll
05-TRANSACTIONS-T05DatabaseRollback, Constraints, Locking und Schema-Kompatibilität mit produktionsnahen Daten prüfen.DB-Integrationstest
05-TRANSACTIONS-F01Fokussuccess, checked/unchecked failure and rollback assertionsModulspezifischer Testreport

Risikoregister des Moduls

RisikoAuswirkungGegenmaßnahmeGate
partial writes and incorrect rollback rulesNIEDRIGsuccess, checked/unchecked failure and rollback assertionsvor Cutover
Neue Framework-/Library-LaufzeitabhängigkeitenMITTELSBOM, Lizenz-, CVE- und transitive Dependency-Prüfung im Online-Buildvor Release
Legacy-Verhalten war implizit an entfernte Library gekoppeltMITTELContract-/Golden-Master-Tests und gezielte Fehlerpfadevor Abschaltung

Rollback und Koexistenz

TransferService kann auf den LegacyTransferService delegieren; niemals beide Implementierungen gleichzeitig schreibend ausführen.

Abbruchkriterien

  • Fachlicher Golden-Master weicht ab.
  • Daten-, Nachrichten- oder API-Kompatibilität ist ungeklärt.
  • Fehlerquote, Latenz oder Ressourcenverbrauch überschreiten das vereinbarte Limit.
  • Monitoring oder Rückfallpfad ist nicht funktionsfähig.

Definition of Done

Direkte Arbeitslinks

⌂ Cockpit