CodeGym /Kursy /Spring Data JPA /Starter JPA: DataSource, EntityManagerFactory, Tx

Starter JPA: DataSource, EntityManagerFactory, Tx

Spring Data JPA
Poziom 4, Lekcja 1
Dostępny

1. Infrastruktura przed JPA

Zanim pojawią się encje, aplikacja musi mieć infrastrukturę, która fizycznie umie komunikować się z bazą danych. Jeśli jesteś początkujący, chęć „no to już piszemy @Entity i zapisujemy produkty" jest całkowicie zrozumiała. Ale bez tej infrastruktury budujemy dom z ładnymi tapetami i bez fundamentów. Trzyma się dokładnie do pierwszego wiatru, czyli do pierwszego błędu połączenia.

W tym wykładzie porządkujemy sobie w głowie: jakie dokładnie komponenty uruchamiają się w aplikacji Spring Boot, gdy dodajesz starter JPA. Naszym celem jest usunięcie poczucia „magii startera" i zastąpienie go normalnym inżynierskim obrazem: kto za co odpowiada, jak komponenty są ze sobą powiązane i jak przekonać się, że naprawdę istnieją w kontekście Spring.

Tu nie omawiamy samego URL, loginu i hasła. Teraz ważniejsze jest zrozumienie, co dokładnie pojawia się w aplikacji i dlaczego bez tego JPA nie wystartuje.

2. Skład spring-boot-starter-data-jpa

Gdy dodajesz zależność spring-boot-starter-data-jpa, nie po prostu „podłączasz bibliotekę do adnotacji @Entity". Mniej więcej mówisz Spring Boot: „Drogi Boot, chcę stos JPA do pracy z danymi, proszę zbuduj mi infrastrukturę". I Boot zaczyna to robić, opierając się na dwóch rzeczach: obecności potrzebnych klas w classpath i ustawieniach, które podamy w następnym wykładzie.

Przydatne jest od razu trzymanie w głowie dwóch sensów startera. Po pierwsze, podciąga właściwy zestaw zależności: JPA, ORM, Spring Data i transakcje. Po drugie, uruchamia łańcuch auto-konfiguracji, który tworzy beany jak DataSource, EntityManagerFactory i PlatformTransactionManager. To właśnie ta „magia", która jest w rzeczywistości całkiem przewidywalna, jeśli wiesz, co dokładnie powinno się pojawić.

Minimalny fragment zależności Gradle na dziś wygląda tak:

dependencies {
    // JPA + Spring Data + transakcje + auto-konfiguracja infrastruktury
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")

    // Sterownik potrzebny na etapie uruchamiania/działania aplikacji, ale zwykle nie do kompilacji
    runtimeOnly("org.postgresql:postgresql")
}

Zwróć uwagę na słowo runtimeOnly przy sterowniku PostgreSQL. To nie „kaprys autora kursu" i nie estetyka. To dosłownie coś, bez czego Spring Boot może zbudować piękną infrastrukturę JPA — a potem i tak się wysypać, bo nie ma czym rozmawiać z PostgreSQL.

3. Komponenty stosu JPA

Poniżej będą krótkie fragmenty weryfikacyjne. To nie część stałego szkieletu projektu, lecz jednorazowe sprawdzenia: zobaczyli prawdziwy bean, zrozumieli jego rolę i usunęli kod. Tak łatwiej zdjąć poczucie magii i nie zamieniać main() w magazyn diagnostycznych println-ów.

Sterownik JDBC dla PostgreSQL

O sterowniku łatwo zapomnieć: w przykładach internetowych jest on „gdzieś tam już gotowy", i wszystko jakby działa samo. Ale jeśli wyjaśniać uczciwie, sterownik — to tłumacz między aplikacją a bazą danych. PostgreSQL nie rozumie obiektów Java, a Spring nie umie telepatycznie czytać tabel. Potrzebna jest warstwa, która mówi z bazą w jej protokole i implementuje standardowe API JDBC.

W twojej aplikacji sterownik zazwyczaj nie figuruje bezpośrednio w kodzie. Nie tworzysz new org.postgresql.Driver() (i chwała Bogu). Ale przy starcie aplikacji Spring Boot musi zdołać zrozumieć: „Okej, baza — PostgreSQL, znaczy sterownik taki-to". I dalej przez JDBC tworzone są prawdziwe połączenia, przez które wysyłane są polecenia SQL.

Przydatna sprawdzian: jeśli dodałeś spring-boot-starter-data-jpa, ale zapomniałeś sterownika PostgreSQL, aplikacja może przejść część startu i potem wysypać się z błędem „no suitable driver". W tym momencie warto nie kląć na ORM, lecz spokojnie powiedzieć sobie: „Okej, infrastrukturę JPA mam tylko w teorii, ale transportu do bazy nie ma".

Łańcuch znaczeń powinien ułożyć się w głowie tak: PostgreSQL (serwer) ← sterownik JDBC ← DataSource ← JPA. Bez środkowego ogniwa reszta zamienia się w filozofię.

DataSource: skąd biorą się połączenia

DataSource — jeden z głównych obiektów dzisiejszego dnia, bo odpowiada za prostą i surową rzecz: „daj mi połączenie z bazą". W świecie Javy podstawową jednostką komunikacji z bazą jest Connection. Tworzenie go jest drogie, mnożenia na każdy kaprys — nie można, zamykać — trzeba ostrożnie. Dlatego w normalnej aplikacji prawie nigdy nie tworzysz połączeń ręcznie: dostajesz je przez DataSource, a dalej infrastruktura zarządza ich cyklem życia.

Przydatna analogia (z lekką autoironią): baza danych — to restauracja, Connection — to stolik, a DataSource — to hostessa przy wejściu. Nie musisz wpadać do kuchni i szukać wolnego stolika samemu. Mówisz: „Poproszę stolik dla dwóch" — i albo cię sadzają, albo grzecznie informują „miejsc nie ma" (i to też ważna informacja).

Ważne jest rozróżnienie kilku podobnych słów, bo właśnie na nich początkujący się potykają:

Termin Co to jest Przybliżony „czas życia"
PostgreSQL proces/serwer bazy danych (w naszym przypadku — kontener) dopóki działa docker compose up
sterownik JDBC biblioteka w aplikacji tyle samo, co żyje aplikacja
DataSource fabryka/dostawca połączeń zwykle jeden bean na całą aplikację
Connection konkretne połączenie krótko: „na czas pracy z bazą" (często — wewnątrz transakcji)

Najfajniejsze: Spring Boot po dodaniu stosu JPA i poprawnych ustawieniach tworzy DataSource automatycznie, i możesz go dosłownie zobaczyć w kontekście. Poniżej — właśnie jednorazowa weryfikacja, żeby zdjąć poczucie magii. Trzymać takiego kodu w main() jako stały szkielet projektu — nie ma sensu.

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) {
        // Jednorazowo podnosimy kontekst Spring, żeby zobaczyć,
        // jakie infrastrukturalne beany naprawdę się stworzyły
        var context = SpringApplication.run(ShopDataJpaApplication.class, args);

        // Patrzymy, jaki DataSource się podniósł (zazwyczaj to HikariCP)
        System.out.println(context.getBean(DataSource.class).getClass().getName());
        // com.zaxxer.hikari.HikariDataSource

        // Patrzymy, jaki menedżer transakcji jest używany dla JPA
        System.out.println(context.getBean(PlatformTransactionManager.class).getClass().getName());
        // org.springframework.orm.jpa.JpaTransactionManager
    }
}

Zobaczyli prawdziwe klasy beanów — i to wystarczy. Sens nie polega na tym, żeby na zawsze osiedlić System.out.println w punkcie wejścia, lecz na tym, żeby raz zobaczyć: infrastruktura naprawdę się podniosła.

Tak, po drodze zobaczyliśmy już Hikari i JpaTransactionManager. Ale nie trzeba się bać: w tym wykładzie nie „studiujemy HikariCP", po prostu odnotowujemy fakt — to są prawdziwe obiekty, a nie bajkowe stworzenia z lasu auto-konfiguracji.

Jeszcze jeden mały, ale pokazowy fragment: jeśli DataSource się podniósł, możesz pobrać Connection i sprawdzić, do czego jest naprawdę podłączony. To taka sama tymczasowa niskopoziomowa weryfikacja, a nie stały styl punktu wejścia.

import java.sql.Connection;

import javax.sql.DataSource;

public class DbSmoke {

    public static void ping(DataSource dataSource) throws Exception {
        // Ważne: połączenie musimy zamknąć obowiązkowo.
        // Dla puli oznacza to "zwróć do puli", a nie "zniszcz fizyczne połączenie".
        try (Connection c = dataSource.getConnection()) {
            // Szybka weryfikacja: jaki URL naprawdę używa JDBC
            System.out.println(c.getMetaData().getURL()); // jdbc:postgresql://localhost:5432/shop

            // Jeszcze jeden prosty smoke-check: czy połączenie jest żywe?
            System.out.println(c.isValid(2));             // true
        }
    }
}

Tu ważne, że połączenie jest zamykane przez try-with-resources. Nawet jeśli połączenia faktycznie pochodzą z puli, i tak trzeba je zamykać: dla puli oznacza to „zwróć połączenie z powrotem", a nie „zniszcz je fizycznie". Ale to już płynny mostek do następnego wykładu o puli.

EntityManagerFactory: skąd bierze się EntityManager

Po DataSource łatwo zdecydować: „no dobrze, podłączyłem się do bazy". I to prawda — ale tylko na poziomie JDBC. JPA — to następne piętro. W tym świecie podstawowym obiektem roboczym jest EntityManager. Pozwala pracować z encjami i zapytaniami nie jak z „surowym SQL w stringach", lecz przez model ORM.

Ale EntityManager nie tworzy się sam. Tworzy go fabryka — EntityManagerFactory. I to ważne ułożyć w głowie z wyprzedzeniem, żeby później nie mylić poziomów infrastruktury.

Grubsza analogia, bez wchodzenia we wnętrzności: EntityManagerFactory — to ciężki obiekt, który tworzy się przy starcie aplikacji i żyje długo. A EntityManager — to bardziej „krótkożyjący" obiekt, używany wewnątrz operacji pracy z danymi, często wewnątrz transakcji. Fabryka istnieje po to, żeby nie budować całego świata JPA od nowa dla każdego żądania.

Sprawdzić, że EntityManagerFactory naprawdę jest w kontekście, można tak. I to też wygodniej postrzegać jako krótką diagnostykę, a nie nową obowiązkową klasę projektu.

import jakarta.persistence.EntityManagerFactory;

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

public class JpaInfraCheck {

    public static void check(ApplicationContext context) {
        // Sprawdzamy obecność standardowego beana po nazwie (typowy default Spring Boot)
        System.out.println(context.containsBean("entityManagerFactory")); // true

        // I osobno sprawdzamy, że bean jest dostępny po typie
        System.out.println(context.getBean(EntityManagerFactory.class).getClass().getName());
        // org.hibernate.internal.SessionFactoryImpl (lub podobna klasa Hibernate)
    }
}

Tu może dziwić linia z klasą Hibernate. I to normalne: JPA — to specyfikacja, a Hibernate — implementacja. Czyli EntityManagerFactory jako interfejs JPA masz, ale „pod spodem" będzie to obiekt Hibernate.

Jeszcze jedna ważna myśl: EntityManagerFactory nie „zastępuje DataSource". Jest zbudowany nad DataSource i używa go, żeby pobierać połączenia. Czyli DataSource — to transport, a EntityManagerFactory — już „stacja metra", która organizuje, jak po tym transporcie jeździć w kategoriach ORM.

Na razie wystarczy odnotować: po dodaniu startera aplikacja ma infrastrukturę JPA w postaci fabryki. W kolejnych dniach dodamy encje i zobaczymy, jak ta fabryka zaczyna pracować „w sprawie".

PlatformTransactionManager: menedżer transakcji

Trzeci kluczowy komponent — menedżer transakcji. Na poziomie SQL znamy już model: begin → kilka operacji → commit/rollback. Ale w Spring nikt nie pisze commit() ręcznie w każdym serwisie — inaczej byłby to kurs „jak cierpieć elegancko". Zamiast tego Spring daje komponent infrastrukturalny, który umie rozpoczynać i kończyć transakcje oraz przywiązywać je do wykonania twoich metod.

Właśnie dlatego menedżer transakcji — to część infrastruktury, a nie logiki biznesowej. Nie wie, co to „złożyć zamówienie" ani „zmniejszyć stany". Wie tylko granice transakcji, reguły i wynik wykonania. To jak skrupulatny księgowy: nieważne, co kupujesz, ważne, że operacja albo jest przeprowadzona, albo się cofnęła.

W Spring ogólny interfejs nazywa się PlatformTransactionManager. Dla stosu JPA typowa implementacja — JpaTransactionManager. I historia jest taka sama: typ beana można raz zobaczyć w kontekście i już nie wierzyć na słowo. Jak poprzednio, to nie powód, żeby zamieniać aplikację w wieczny rytuał println-ów.

Mini-weryfikacja (podobna do tego, co już robiliśmy):

import org.springframework.transaction.PlatformTransactionManager;

public class TxInfraCheck {

    public static void check(PlatformTransactionManager txManager) {
        // Sprawdzamy, że Spring naprawdę dał implementację JPA menedżera transakcji
        System.out.println(txManager.getClass().getName());
        // org.springframework.orm.jpa.JpaTransactionManager
    }
}

Na razie nie używamy @Transactional w tym dniu — to będzie później, w module o transakcjach. Ale już teraz przydatne jest rozumienie: gdy tylko dodałeś starter JPA, Spring Boot przygotowuje podstawę pod przyszłe transakcje. I gdy potem postawisz @Transactional na metodzie serwisu, Spring będzie opierał się właśnie na tym menedżerze, żeby robić prawdziwe commit/rollback na poziomie bazy.

4. Łańcuch: kod → transakcje → JPA → JDBC → PostgreSQL

Teraz mamy trzy komponenty, i początkujący często widzi je jako trzy osobne „dziwne beany". Żeby złożyły się w zrozumiały obraz, zbierzmy je w jednym schemacie. Wyobraź sobie, że w przyszłości będziesz miał serwis, który wykonuje jakąś operację z danymi. Co w tym momencie dzieje się wewnątrz aplikacji?

Poniżej — uproszczony schemat. Nie pokazuje subtelności jak persistence context (to będzie dużo później), ale daje właściwy szkielet rozumienia.

flowchart TD
    A["Twój kod service/repository"] --> B["PlatformTransactionManager JpaTransactionManager"]
    B --> C["EntityManagerFactory"]
    C --> D["EntityManager"]
    D --> E["DataSource"]
    E --> F["Połączenie JDBC"]
    F --> G[(PostgreSQL)]

Sens łańcucha jest taki: menedżer transakcji ustawia ramę transakcyjną wokół operacji; infrastruktura JPA zapewnia działanie ORM wewnątrz tej ramy; DataSource wydaje prawdziwe połączenie; przez to połączenie SQL trafia do PostgreSQL. Tak, później zobaczymy niuanse jak cache pierwszego poziomu, dirty checking i flush. Ale jeśli teraz rozumiesz podstawową „rurę", pozbawisz się połowy mistycznych lęków wokół JPA.

Dla utrwalenia warto jeszcze raz krótko zestawić odpowiedzialności:

Komponent Główne pytanie, na które odpowiada
DataSource „Skąd wziąć połączenie z bazą?"
EntityManagerFactory „Jak stworzyć kontekst JPA do pracy z encjami?"
PlatformTransactionManager „Jak poprawnie rozpocząć i zakończyć transakcję wokół operacji?"

I zauważ: te komponenty nie „rywalizują" ze sobą i nie duplikują ról. Stoją jeden na drugim, jak warstwy ciasta. Nie tego ciasta, które „tylko kawałeczek", lecz tego, po którym mówisz: „no dobra, jeszcze jeden".

5. Typowe błędy przy uruchamianiu stosu JPA

Błąd nr 1: uważanie, że starter „podłącza bazę samodzielnie", nawet jeśli nie podałeś parametrów połączenia.
spring-boot-starter-data-jpa naprawdę podnosi infrastrukturę, ale i tak potrzebuje konkretnego URL JDBC, loginu i hasła. Jeśli nie podałeś ustawień, Boot nie zdoła stworzyć działającego DataSource. To nie „kaprys frameworka", to po prostu brak adresu, pod który jechać.

Błąd nr 2: mylenie sterownika JDBC i DataSource.
Sterownik — to biblioteka, która umie rozmawiać z PostgreSQL po protokole. DataSource — to obiekt w twojej aplikacji, który wydaje połączenia. Czasem studenci mówią: „mam DataSource, więc sterownik nie jest potrzebny". W praktyce bez sterownika DataSource albo się nie stworzy, albo nie zdoła stworzyć połączenia. Sterownik — to jak koła w samochodzie: możesz pięknie urządzić wnętrze, ale bez kół daleko nie zajedziesz.

Błąd nr 3: myślenie, że EntityManagerFactory — to „jeszcze jedna nazwa dla połączenia z bazą".
EntityManagerFactory dotyczy infrastruktury JPA, a nie „połączenia sieciowego". Żyje nad DataSource i pomaga pracować z encjami. Jeśli pomiesza się te poziomy w głowie, później zaczną się trudności: gdzie konfiguruje się połączenie, a gdzie żyje ORM.

Błąd nr 4: próba „sprawdzenia wszystkiego" przez kod biznesowy, zanim sprawdzisz infrastrukturę.
Bardzo typowa historia: student od razu pisze encję, repozytorium i serwis, a potem aplikacja się wysypuje, bo nie jest podłączona do bazy. W efekcie debugujesz naraz cztery problemy (kod + konfiguracja + baza + zależności) zamiast jednego. Dużo spokojniej jest najpierw zrobić smoke-check: czy jest DataSource, czy wydaje połączenie, czy podniósł się menedżer transakcji.

Błąd nr 5: zamienianie tymczasowej diagnostyki w stały styl.
Drukowanie beanów z main() — to dobre latarnia edukacyjna, ale zły styl dla stałego życia projektu. Używaj tego jako narzędzia rozumienia: sprawdziłeś, przekonałeś się, usunąłeś. Nie chcemy, żeby main() z czasem zamienił się w wysypisko eksperymentów, bo dalej będzie trudno utrzymać szkielet projektu w czystości.

1
Zadanie
Spring Data JPA, poziom 4, lekcja 1
Niedostępne
Wypisywanie klas ziaren infrastruktury stosu JPA
Wypisywanie klas ziaren infrastruktury stosu JPA
1
Zadanie
Spring Data JPA, poziom 4, lekcja 1
Niedostępne
Diagnostyka `entityManagerFactory`, menedżera transakcji i JDBC URL
Diagnostyka `entityManagerFactory`, menedżera transakcji i JDBC URL
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION