Spring Data JDBC für klare Aggregate
Spring Data JDBC ist eine schlankere, explizitere Alternative zu JPA für relationale Aggregate ohne komplexen Persistence Context.
Fachliche Einordnung
Es eignet sich, wenn du SQL-nahe Kontrolle, einfache Aggregate und weniger Hibernate-Magie möchtest. Änderungen sind direkter und oft leichter zu erklären.
Enterprise-Merksatz: Spring Data JDBC ist eine schlankere, explizitere Alternative zu JPA für relationale Aggregate ohne komplexen Persistence Context.
Technische Darstellung
Kernkonzepte
- Aggregate werden vollständig geladen und gespeichert.
- Kein Lazy Loading und kein First-Level-Cache wie bei JPA.
- Repository-Abstraktion mit JDBC-Mapping.
- @MappedCollection für Kindobjekte.
- SQL bleibt besser sichtbar und kontrollierbar.
Wann einsetzen?
- Du willst weniger ORM-Komplexität.
- Dein Datenmodell ist relational, aber fachlich eher aggregate-orientiert.
- Du willst Verhalten gut vorhersagen und SQL leichter optimieren.
Typische Fehler und Risiken
- Zu große Aggregate verursachen teure Writes.
- JPA-Denken wie Lazy Loading funktioniert hier nicht.
- Komplexe Legacy-Schema-Relationen können aufwändig sein.
Legacy- und Modernisierungssicht
Für alte JDBC-DAOs ist Spring Data JDBC oft ein natürlicherer Zwischenschritt als JPA, weil SQL-Nähe und Transaktionsdenken erhalten bleiben.
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.
@Table("purchase_order")
public class PurchaseOrder {
@Id private UUID id;
private String customerNumber;
private OrderStatus status;
@MappedCollection(idColumn = "order_id")
private Set<PurchaseOrderLine> lines = new LinkedHashSet<>();
public void addLine(String sku, int quantity) {
if (status != OrderStatus.DRAFT) throw new IllegalStateException("order locked");
lines.add(new PurchaseOrderLine(sku, quantity));
}
}
interface PurchaseOrderJdbcRepository extends CrudRepository<PurchaseOrder, UUID> {}
@Service
class DraftOrderService {
@Transactional
public UUID createDraft(String customer) {
return repository.save(PurchaseOrder.draft(customer)).getId();
}
}
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?