Spring Transactions, JTA und Transaktionsgrenzen
Spring Transactions abstrahiert lokale und verteilte Transaktionen über PlatformTransactionManager, @Transactional und konsistente Rollback-Regeln.
Fachliche Einordnung
Transaktionen gehören zu den wichtigsten Enterprise-Themen. Fehlerhafte Grenzen erzeugen Dateninkonsistenzen, Locking-Probleme und schwer reproduzierbare Bugs.
Enterprise-Merksatz: Spring Transactions abstrahiert lokale und verteilte Transaktionen über PlatformTransactionManager, @Transactional und konsistente Rollback-Regeln.
Technische Darstellung
Kernkonzepte
- PlatformTransactionManager als Strategie für JDBC, JPA oder JTA.
- Propagation REQUIRED, REQUIRES_NEW, MANDATORY und NESTED.
- Isolation Level und Locking.
- Rollback bei RuntimeException standardmäßig.
- Transaktionale Outbox statt verteiltem 2PC, wenn Microservices beteiligt sind.
Wann einsetzen?
- Du speicherst mehrere Änderungen als fachliche Einheit.
- Du migrierst JTA/EJB-Transaktionen.
- Du willst Fehler- und Retry-Verhalten kontrollieren.
Typische Fehler und Risiken
- @Transactional auf private Methoden oder Self-invocation.
- Remote Calls innerhalb langer DB-Transaktion.
- REQUIRES_NEW ohne fachliche Begründung.
Legacy- und Modernisierungssicht
EJB CMT wird meist über @Transactional ersetzt. Für echte XA-Fälle bleibt JTA möglich, aber moderne Architekturen bevorzugen Outbox, Saga oder idempotente Events.
Ausführliches Beispiel
Das Beispiel zeigt bewusst nicht nur Annotationen, sondern auch die Verantwortung der Schicht. In echten Projekten sollte der technische Spring-Code an Adapter- oder Konfigurationsrändern bleiben, während die Fachlogik testbar und möglichst frameworkarm bleibt.
@Service
class ShipmentBookingService {
@Transactional
public void bookShipment(BookShipment command) {
Shipment shipment = shipmentRepository.reserve(command.orderId(), command.address());
outboxRepository.save(OutboxEvent.of("ShipmentReserved", shipment.id()));
// Kein externer HTTP-Aufruf hier: erst committen, dann asynchron aus Outbox senden.
}
}
@Component
class OutboxPublisher {
@Scheduled(fixedDelayString = "PT5S")
@Transactional
void publishPending() {
outboxRepository.findNextBatch(100).forEach(event -> {
kafkaTemplate.send(event.topic(), event.key(), event.payload());
event.markPublished();
});
}
}
Checkliste für Reviews
- Ist die Verantwortung des Bausteins klar: Framework steuert Lebenszyklus, Library wird gezielt benutzt?
- Ist die Fachlogik außerhalb von Controller, Listener, Repository-Implementierung oder Konfiguration?
- Sind Fehlerfälle, Timeouts, Security, Monitoring und Tests sichtbar modelliert?
- Gibt es klare Grenzen zwischen DTO, Domäne, Persistence und Infrastruktur?