CodeGym /Kurse /Spring Data JPA /JPA-Starter: DataSource, EntityManagerFactory, Tx

JPA-Starter: DataSource, EntityManagerFactory, Tx

Spring Data JPA
Level 4, Lektion 1
Verfügbar

1. Infrastruktur vor JPA

Bevor Entities erscheinen, braucht die Anwendung eine Infrastruktur, die physisch mit der Datenbank kommunizieren kann. Wenn du Einsteiger bist, ist der Wunsch „Lasst uns endlich @Entity schreiben und Produkte speichern" absolut verständlich. Aber ohne diese Infrastruktur bauen wir ein Haus mit schöner Tapete und ohne Fundament. Es hält bis zum ersten Wind – also bis zum ersten Verbindungsfehler.

In dieser Lektion bringen wir Ordnung in den Kopf: Welche Komponenten genau werden in einer Spring-Boot-Anwendung hochgefahren, wenn du den JPA-Starter hinzufügst? Unser Ziel ist, das Gefühl der „Starter-Magie" zu beseitigen und es durch ein normales technisches Bild zu ersetzen: Wer ist für was zuständig, wie hängen die Komponenten zusammen und wie kannst du überprüfen, dass sie im Spring-Kontext tatsächlich vorhanden sind?

Hier befassen wir uns nicht mit der URL, dem Login und dem Passwort selbst. Wichtiger ist jetzt zu verstehen, was genau in der Anwendung erscheint und warum JPA ohne all das nicht starten kann.

2. Bestandteile von spring-boot-starter-data-jpa

Wenn du die Abhängigkeit spring-boot-starter-data-jpa hinzufügst, „verbindest du nicht einfach eine Bibliothek für @Entity-Annotationen". Du sagst Spring Boot ungefähr: „Lieber Boot, ich möchte den JPA-Stack für die Arbeit mit Daten, bitte richte mir die Infrastruktur ein". Und Boot beginnt damit, gestützt auf zwei Dinge: das Vorhandensein der benötigten Klassen im classpath und die Einstellungen, die wir in der nächsten Lektion definieren werden.

Es ist hilfreich, sofort zwei Bedeutungen des Starters im Kopf zu behalten. Erstens zieht er den richtigen Satz an Abhängigkeiten heran: JPA, ORM, Spring Data und Transaktionen. Zweitens startet er eine Kette von Auto-Konfigurationen, die Beans wie DataSource, EntityManagerFactory und PlatformTransactionManager erstellen. Das ist die „Magie", die eigentlich recht vorhersehbar ist, wenn man weiß, was erscheinen soll.

Das minimale Gradle-Abhängigkeitsfragment für heute sieht so aus:

dependencies {
    // JPA + Spring Data + Transaktionen + Auto-Konfiguration der Infrastruktur
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")

    // Der Treiber wird zur Laufzeit der Anwendung benötigt, aber üblicherweise nicht für die Kompilierung
    runtimeOnly("org.postgresql:postgresql")
}

Beachte das Wort runtimeOnly beim PostgreSQL-Treiber. Das ist keine „Laune des Kursautors" und keine Ästhetik. Das ist buchstäblich das, ohne das Spring Boot eine schöne JPA-Infrastruktur hochfahren kann – und dann trotzdem abstürzt, weil es nichts hat, um mit PostgreSQL zu kommunizieren.

3. Komponenten des JPA-Stacks

Weiter folgen kurze Überprüfungssnippets. Das ist kein dauerhafter Teil des Projektgerüsts, sondern Einweg-Überprüfungen: einmal den echten Bean gesehen, seine Rolle verstanden und den Code entfernt. So lässt sich das Magie-Gefühl leichter beseitigen, ohne main() zum println-Diagnostik-Lager zu machen.

JDBC-Treiber für PostgreSQL

Den Treiber vergisst man leicht: In Internet-Beispielen ist er oft „irgendwo schon vorhanden", und alles scheint von selbst zu laufen. Aber wenn man es ehrlich erklärt: Der Treiber ist ein Übersetzer zwischen der Anwendung und der Datenbank. PostgreSQL versteht keine Java-Objekte, und Spring kann keine Tabellen telepathisch lesen. Es wird eine Schicht benötigt, die mit der DB über ihr Protokoll spricht und die Standard-JDBC-API implementiert.

In deiner Anwendung erscheint der Treiber normalerweise nicht direkt im Code. Du erstellst kein new org.postgresql.Driver() (und das ist gut so). Aber beim Start der Anwendung muss Spring Boot verstehen können: „Also, die Datenbank ist PostgreSQL, also ist der Treiber so-und-so". Und danach werden über JDBC echte Verbindungen hergestellt, über die SQL-Befehle gesendet werden.

Nützliche Selbstkontrolle: Wenn du spring-boot-starter-data-jpa hinzugefügt, aber den PostgreSQL-Treiber vergessen hast, kann die Anwendung einen Teil des Starts durchlaufen und danach mit einem Fehler wie „no suitable driver" abstürzen. In diesem Moment ist es hilfreich, nicht den ORM zu verfluchen, sondern sich ruhig zu sagen: „Okay, ich habe die JPA-Infrastruktur nur theoretisch, und es gibt keinen Transport zur Datenbank".

Die Bedeutungskette sollte sich so im Kopf festsetzen: PostgreSQL (Server) ← JDBC-Treiber ← DataSource ← JPA. Ohne das Mittelglied wird der Rest zur Philosophie.

DataSource: woher die Verbindungen kommen

DataSource ist eines der wichtigsten Objekte des heutigen Tages, weil es für eine einfache und ernste Sache zuständig ist: „Gib mir eine Verbindung zur Datenbank". In der Java-Welt ist die grundlegende Einheit der DB-Kommunikation eine Connection. Sie zu erstellen ist teuer, man kann nicht für jeden Kleinkram eine neue anlegen, und man muss sie sorgfältig schließen. Deshalb erstellst du in einer normalen Anwendung Verbindungen fast nie manuell: Du bekommst sie über DataSource, und die Infrastruktur verwaltet deren Lebenszyklus.

Eine nützliche Analogie (mit leichter Selbstironie): Die Datenbank ist ein Restaurant, Connection ist ein Tisch, und DataSource ist die Hostess am Eingang. Du solltest nicht in die Küche stürmen und selbst einen freien Tisch suchen. Du sagst: „Ich hätte gerne einen Tisch für zwei" – und entweder wirst du platziert, oder höflich gesagt „Kein Platz" (auch das ist wichtige Information).

Es ist wichtig, mehrere ähnliche Begriffe zu unterscheiden, weil Einsteiger darüber stolpern:

Begriff Was das ist Ungefähre „Lebensdauer"
PostgreSQL DB-Prozess/Server (in unserem Fall – Container) Solange docker compose up läuft
JDBC driver Bibliothek in der Anwendung Genauso lang wie die Anwendung läuft
DataSource Fabrik/Anbieter von Verbindungen Üblicherweise ein Bean für die gesamte Anwendung
Connection Konkrete Verbindung Kurz: „für die Dauer der DB-Arbeit" (oft – innerhalb einer Transaktion)

Das Schönste: Spring Boot erstellt DataSource nach dem Hinzufügen des JPA-Stacks und korrekten Einstellungen automatisch, und du kannst es im Kontext buchstäblich sehen. Unten ist genau eine Einweg-Überprüfung, um das Magie-Gefühl zu beseitigen. Solchen Code dauerhaft in main() als ständiges Projektgerüst zu halten, ist nicht nötig.

import javax.sql.DataSource;

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

@SpringBootApplication
public class ShopDataJpaApplication {

    public static void main(String[] args) {
        // Einmalig den Spring-Kontext hochfahren, um zu sehen,
        // welche Infrastruktur-Beans wirklich erstellt wurden
        var context = SpringApplication.run(ShopDataJpaApplication.class, args);

        // Prüfen, welche DataSource hochgefahren wurde (üblicherweise HikariCP)
        System.out.println(context.getBean(DataSource.class).getClass().getName());
        // com.zaxxer.hikari.HikariDataSource

        // Prüfen, welcher Transaktionsmanager für JPA verwendet wird
        System.out.println(context.getBean(PlatformTransactionManager.class).getClass().getName());
        // org.springframework.orm.jpa.JpaTransactionManager
    }
}

Die echten Klassen der Beans gesehen – das reicht bereits. Der Sinn ist nicht, dauerhaft System.out.println im Einstiegspunkt anzusiedeln, sondern einmal zu sehen: Die Infrastruktur ist wirklich hochgefahren.

Ja, dabei haben wir schon Hikari und JpaTransactionManager gesehen. Aber keine Panik: In dieser Lektion „studieren wir nicht HikariCP", sondern stellen nur einen Fakt fest – das sind echte Objekte, keine Märchenwesen aus dem Wald der Auto-Konfiguration.

Noch ein kleines, aber anschauliches Snippet: Wenn DataSource hochgefahren ist, kannst du eine Connection holen und sehen, womit sie wirklich verbunden ist. Das ist ebenfalls eine temporäre Low-Level-Überprüfung, kein dauerhafter Einstiegspunkt-Stil.

import java.sql.Connection;

import javax.sql.DataSource;

public class DbSmoke {

    public static void ping(DataSource dataSource) throws Exception {
        // Wichtig: Verbindung muss geschlossen werden.
        // Für den Pool bedeutet das „in den Pool zurückgeben", nicht „physische Verbindung zerstören".
        try (Connection c = dataSource.getConnection()) {
            // Schnelle Prüfung: welche URL JDBC wirklich verwendet
            System.out.println(c.getMetaData().getURL()); // jdbc:postgresql://localhost:5432/shop

            // Noch eine einfache Smoke-Prüfung: ist die Verbindung aktiv?
            System.out.println(c.isValid(2));             // true
        }
    }
}

Wichtig ist hier, dass die Verbindung über try-with-resources geschlossen wird. Auch wenn Verbindungen aus einem Pool kommen, muss man sie trotzdem schließen: Für den Pool bedeutet das „Verbindung zurückgeben", nicht „sie physisch zerstören". Aber das ist bereits eine sanfte Brücke zur nächsten Lektion über den Pool.

EntityManagerFactory: woher der EntityManager kommt

Nach DataSource liegt die Entscheidung nahe: „Gut, ich bin mit der Datenbank verbunden". Und das stimmt – aber nur auf JDBC-Ebene. JPA ist das nächste Stockwerk. In dieser Welt ist das zentrale Arbeitsobjekt der EntityManager. Er ermöglicht die Arbeit mit Entities und Abfragen nicht als „rohes SQL in Strings", sondern über das ORM-Modell.

Aber EntityManager wird nicht von alleine erstellt. Ihn erstellt eine Fabrik – EntityManagerFactory. Das ist wichtig, sich schon jetzt zu merken, damit man später die Infrastrukturebenen nicht verwechselt.

Grob gesagt, ohne in die Interna einzutauchen: EntityManagerFactory ist ein schweres Objekt, das beim Anwendungsstart erstellt wird und lang lebt. Und EntityManager ist ein eher „kurzlebiges" Objekt, das innerhalb von Daten-Operationen verwendet wird, oft innerhalb einer Transaktion. Die Fabrik wird gebraucht, damit die JPA-Welt nicht für jede Anfrage neu aufgebaut werden muss.

So kannst du überprüfen, dass EntityManagerFactory wirklich im Kontext vorhanden ist. Auch das ist besser als kurze Diagnose zu betrachten und nicht als neue Pflichtklasse des Projekts.

import jakarta.persistence.EntityManagerFactory;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;

public class JpaInfraCheck {

    public static void check(ApplicationContext context) {
        // Vorhandensein des Standard-Beans nach Name prüfen (typischer Spring-Boot-Default)
        System.out.println(context.containsBean("entityManagerFactory")); // true

        // Und separat prüfen, dass der Bean nach Typ erreichbar ist
        System.out.println(context.getBean(EntityManagerFactory.class).getClass().getName());
        // org.hibernate.internal.SessionFactoryImpl (oder ähnliche Hibernate-Klasse)
    }
}

Hier kann der Hibernate-Klassenname irritieren. Das ist normal: JPA ist eine Spezifikation, Hibernate eine Implementierung. Das bedeutet: Du hast EntityManagerFactory als JPA-Interface, aber „unter der Haube" ist das ein Hibernate-Objekt.

Noch ein wichtiger Gedanke: EntityManagerFactory „ersetzt nicht DataSource". Es ist über DataSource aufgebaut und verwendet es, um Verbindungen zu holen. Das heißt: DataSource ist der Transport, und EntityManagerFactory ist bereits die „U-Bahn-Station", die organisiert, wie man auf diesem Transport in ORM-Begriffen fährt.

Im Moment reicht es uns festzustellen: Nach dem Einbinden des Starters hat die Anwendung eine JPA-Infrastruktur in Form einer Fabrik. In den nächsten Lektionen werden wir Entities hinzufügen und sehen, wie diese Fabrik wirklich „zur Sache" zu arbeiten beginnt.

PlatformTransactionManager: der Transaktionsmanager

Die dritte Schlüsselkomponente ist der Transaktionsmanager. Auf SQL-Ebene kennen wir bereits das Modell: begin → mehrere Operationen → commit/rollback. Aber in Spring schreibt niemand commit() manuell in jeden Service – das wäre dann ein Kurs „Wie man elegant leidet". Stattdessen bietet Spring eine Infrastrukturkomponente, die Transaktionen starten und abschließen sowie sie an die Ausführung deiner Methoden binden kann.

Deshalb ist der Transaktionsmanager Teil der Infrastruktur, nicht der Geschäftslogik. Er weiß nicht, was „eine Bestellung aufgeben" oder „Bestände verringern" bedeutet. Er kennt nur Transaktionsgrenzen, Regeln und das Ausführungsergebnis. Das ist wie ein akkurater Buchhalter: Ihm ist egal, was du kaufst, wichtig ist, dass der Vorgang entweder durchgeführt oder zurückgerollt wurde.

In Spring heißt das allgemeine Interface PlatformTransactionManager. Für den JPA-Stack ist die typische Implementierung JpaTransactionManager. Und hier ist die Geschichte dieselbe: Den Bean-Typ kann man einmal im Kontext sehen und muss dann nichts mehr auf Treu und Glauben annehmen. Wie vorher ist das kein Anlass, die Anwendung in ein ewiges println-Ritual zu verwandeln.

Mini-Überprüfung (ähnlich wie das, was wir bereits gemacht haben):

import org.springframework.transaction.PlatformTransactionManager;

public class TxInfraCheck {

    public static void check(PlatformTransactionManager txManager) {
        // Prüfen, dass Spring wirklich die JPA-Implementierung des Transaktionsmanagers geliefert hat
        System.out.println(txManager.getClass().getName());
        // org.springframework.orm.jpa.JpaTransactionManager
    }
}

Wir verwenden @Transactional in diesem Tag noch nicht – das kommt später im Transaktionsmodul. Aber schon jetzt ist es hilfreich zu verstehen: Sobald du den JPA-Starter eingebunden hast, bereitet Spring Boot die Grundlage für zukünftige Transaktionen vor. Und wenn du später @Transactional auf eine Service-Methode setzt, wird Spring genau auf diesen Manager zurückgreifen, um echte commit/rollback auf DB-Ebene auszuführen.

4. Kette: Code → Transaktionen → JPA → JDBC → PostgreSQL

Jetzt haben wir drei Komponenten, und ein Einsteiger sieht sie oft als drei separate „seltsame Beans". Damit sie sich zu einem verständlichen Bild zusammenfügen, wollen wir sie in einem Diagramm zusammenbringen. Stell dir vor, dass du in Zukunft einen Service haben wirst, der irgendeine Daten-Operation ausführt. Was passiert in diesem Moment innerhalb der Anwendung?

Unten ist ein vereinfachtes Diagramm. Es zeigt keine Feinheiten wie Persistence Context (das kommt viel später), gibt aber das richtige Grundgerüst des Verständnisses.

flowchart TD
    A["Dein Code service/repository"] --> B["PlatformTransactionManager JpaTransactionManager"]
    B --> C["EntityManagerFactory"]
    C --> D["EntityManager"]
    D --> E["DataSource"]
    E --> F["JDBC Connection"]
    F --> G[(PostgreSQL)]

Die Bedeutung der Kette: Der Transaktionsmanager setzt den Transaktionsrahmen um die Operation; die JPA-Infrastruktur sorgt für den ORM-Betrieb innerhalb dieses Rahmens; DataSource liefert die echte Verbindung; über diese Verbindung geht SQL an PostgreSQL. Ja, später werden wir Nuancen wie First-Level-Cache, Dirty Checking und Flush sehen. Aber wenn du jetzt die grundlegende „Röhre" verstehst, sparst du dir die Hälfte der mystischen JPA-Ängste.

Zur Festigung ist es nützlich, die Verantwortlichkeiten noch einmal kurz gegenüberzustellen:

Komponente Die Hauptfrage, die sie beantwortet
DataSource „Woher bekomme ich eine DB-Verbindung?"
EntityManagerFactory „Wie erstelle ich einen JPA-Kontext für die Arbeit mit Entities?"
PlatformTransactionManager „Wie starte und beende ich korrekt eine Transaktion um eine Operation?"

Und beachte: Diese Komponenten „konkurrieren" nicht miteinander und duplizieren keine Rollen. Sie stehen aufeinander wie Schichten eines Kuchens. Nicht der Kuchen, bei dem man sagt „nur ein Stückchen", sondern der, nach dem man sagt: „Na gut, noch einer".

5. Typische Fehler beim Start des JPA-Stacks

Fehler Nr. 1: Annehmen, dass der Starter „die Datenbank selbst verbindet", auch wenn du keine Verbindungsparameter angegeben hast.
spring-boot-starter-data-jpa baut tatsächlich die Infrastruktur auf, braucht aber trotzdem eine konkrete JDBC-URL, Login und Passwort. Wenn du keine Einstellungen angegeben hast, kann Boot keine funktionierende DataSource erstellen. Das ist keine „Framework-Laune", sondern einfach eine fehlende Adresse, wohin man fahren soll.

Fehler Nr. 2: JDBC-Treiber und DataSource verwechseln.
Der Treiber ist eine Bibliothek, die mit PostgreSQL über das Protokoll sprechen kann. DataSource ist ein Objekt in deiner Anwendung, das Verbindungen liefert. Manchmal sagen Lernende: „Ich habe DataSource, also brauche ich keinen Treiber". In der Praxis wird ohne Treiber DataSource entweder nicht erstellt, oder kann keine Verbindung herstellen. Der Treiber ist wie Reifen an einem Auto: Du kannst den Innenraum schön einrichten, aber ohne Reifen kommst du nicht weit.

Fehler Nr. 3: Denken, dass EntityManagerFactory „ein anderer Name für die DB-Verbindung" ist.
EntityManagerFactory gehört zur JPA-Infrastruktur, nicht zur „Netzwerkverbindung". Es liegt über DataSource und hilft dir, mit Entities zu arbeiten. Wenn du diese Ebenen im Kopf vermischst, entsteht später Verwirrung: Wo wird die Verbindung konfiguriert, und wo lebt der ORM?

Fehler Nr. 4: Versuchen, „alles" über Geschäftscode zu überprüfen, bevor du die Infrastruktur geprüft hast.
Eine sehr typische Geschichte: Der Lernende schreibt sofort Entity, Repository und Service, und dann stürzt die Anwendung ab, weil keine Datenbankverbindung besteht. Am Ende debuggst du gleichzeitig vier Probleme (Code + Konfiguration + Datenbank + Abhängigkeiten) statt einer. Viel ruhiger ist es, zuerst eine Smoke-Prüfung zu machen: Gibt es DataSource, liefert er eine Verbindung, ist der Transaktionsmanager hochgefahren?

Fehler Nr. 5: Temporäre Diagnose in einen dauerhaften Stil umwandeln.
Beans aus main() auszugeben ist eine gute Lernlampe, aber ein schlechter Stil für das dauerhafte Projektleben. Nutze das als Verständniswerkzeug: gesehen, bestätigt, gelöscht. Wir wollen nicht, dass main() mit der Zeit zu einem Experimentenfriedhof wird, sonst wird es schwerer, das Projektgerüst sauber zu halten.

1
Aufgabe
Spring Data JPA, Level 4, Lektion 1
Gesperrt
Ausgabe der Klassennamen der Infrastruktur-Beans des JPA-Stacks
Ausgabe der Klassennamen der Infrastruktur-Beans des JPA-Stacks
1
Aufgabe
Spring Data JPA, Level 4, Lektion 1
Gesperrt
Diagnose von `entityManagerFactory`, Transaktionsmanager und JDBC-URL
Diagnose von `entityManagerFactory`, Transaktionsmanager und JDBC-URL
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION