Skip to content

1. Wprowadzenie

1.1. Objectifs

PDF z dokumentu jest dostępny pod nazwą |TUTAJ|.

Przykłady z dokumentu są dostępne pod adresem |TUTAJ|.

W niniejszym dokumencie zamierzamy zapoznać się z głównymi koncepcjami trwałości danych przy użyciu API i JPA (Java Persistence API). Po przeczytaniu tego dokumentu i przetestowaniu zawartych w nim przykładów czytelnik powinien opanować podstawy niezbędne do samodzielnej pracy.

API JPA to nowa specyfikacja. Stała się dostępna dopiero od wersji JDK 1.5. Warstwa JPA ma swoje miejsce w architekturze wielowarstwowej. Rozważmy dość powszechną architekturę trójwarstwową:

  • warstwa [1], zwana tutaj [ui] (interfejs użytkownika), jest warstwą, która komunikuje się z użytkownikiem za pośrednictwem graficznego interfejsu Swing, interfejsu konsoli lub interfejsu internetowego. Jej rolą jest przekazywanie danych pochodzących od użytkownika do warstwy [2] lub prezentowanie użytkownikowi danych dostarczonych przez warstwę [2].
  • Warstwa [2], zwana tutaj [metier], to warstwa, która stosuje tzw. reguły biznesowe, c.a.d. specyficzna logika aplikacji, bez zwracania uwagi na to, skąd pochodzą przekazywane jej dane ani dokąd trafiają generowane przez nią wyniki.
  • warstwa [3], zwana tutaj [dao] (Data Access Object), jest warstwą, która dostarcza warstwie [2] wcześniej zapisane dane (pliki, bazy danych, ...) oraz zapisuje niektóre wyniki dostarczane przez warstwę [2].
  • Warstwa [JDBC] jest standardową warstwą używaną w Javie do uzyskiwania dostępu do baz danych. Nazywa się ją zazwyczaj sterownikiem JDBC dla SGBD.

Podjęto wiele wysiłków, aby ułatwić programistom pisanie tych różnych warstw. Wśród nich JPA ma na celu ułatwienie tworzenia warstwy [dao], która zarządza tak zwanymi danymi trwałymi, stąd nazwa API (Java Persistence API). Rozwiązaniem, które w ostatnich latach zyskało popularność w tej dziedzinie, jest Hibernate:

Warstwa [Hibernate] znajduje się pomiędzy warstwą [dao] napisaną przez programistę a warstwą [Jdbc]. Hibernate to narzędzie typu ORM (Object Relational Mapping), które stanowi pomost między relacyjnym światem baz danych a światem obiektów przetwarzanych przez Javę. Programista warstwy [dao] nie widzi już warstwy [Jdbc] ani tabel bazy danych, z których treści chce korzystać. Widzi jedynie obiektowy obraz bazy danych, dostarczany przez warstwę [Hibernate]. Połączenie między tabelami bazy danych a obiektami obsługiwanymi przez warstwę [dao] odbywa się głównie na dwa sposoby:

  • poprzez pliki konfiguracyjne typu XML
  • poprzez adnotacje Java w kodzie, technika dostępna dopiero od wersji JDK 1.5

Warstwa [Hibernate] jest warstwą abstrakcyjną, która ma być jak najbardziej przejrzysta. Idealnym rozwiązaniem jest sytuacja, w której programista warstwy [dao] może całkowicie zignorować fakt, że pracuje z bazą danych. Jest to możliwe, o ile to nie on sam pisze konfigurację, która stanowi pomost między światem relacyjnym a światem obiektowym. Konfiguracja tego pomostu jest dość skomplikowana i wymaga pewnej wprawy.

Warstwa obiektów [4], będąca odzwierciedleniem warstwy BD, nazywana jest „kontekstem trwałości”. Warstwa [dao] oparta na Hibernate wykonuje operacje trwałości (CRUD: create – read – update – delete) na obiektach kontekstu trwałości; operacje te są przekształcane przez Hibernate na polecenia SQL. W przypadku operacji zapytania do bazy danych (SQL Select), Hibernate udostępnia programistom język HQL (Hibernate Query Language) do wysyłania zapytań do kontekstu trwałości [4], a nie bezpośrednio do samej bazy danych BD.

Hibernate jest popularny, ale trudny do opanowania. Krzywa uczenia się, często przedstawiana jako łatwa, jest w rzeczywistości dość stroma. Gdy tylko mamy do czynienia z bazą danych zawierającą tabele o relacjach typu „jeden do wielu” lub „wiele do wielu”, konfiguracja mostu relacyjno-obiektowego nie jest w zasięgu możliwości zwykłego początkującego. Błędy konfiguracyjne mogą wówczas prowadzić do niskiej wydajności aplikacji.

W środowisku komercyjnym istniał produkt równoważny z Hibernate o nazwie Toplink:

W obliczu sukcesu produktów ORM firma Sun, twórca Javy, postanowiła ustandaryzować warstwę ORM za pomocą specyfikacji o nazwie JPA, która pojawiła się wraz z Javą 5. Specyfikacja JPA została zaimplementowana zarówno w produkcie Toplink, jak i Hibernate. Toplink, który pierwotnie był produktem komercyjnym, stał się od tego czasu oprogramowaniem wolnym. Dzięki JPA poprzednia architektura przybiera następujący kształt:

Warstwa [dao] komunikuje się teraz ze specyfikacją JPA, czyli zestawem interfejsów. Programista zyskał dzięki temu na standaryzacji. Wcześniej, jeśli zmieniał swoją warstwę ORM, musiał również zmienić warstwę [dao], która została napisana w celu komunikacji z konkretną warstwą ORM. Teraz napisze warstwę [dao], która będzie współpracować z warstwą JPA. Niezależnie od produktu, w którym zostanie ona zaimplementowana, interfejs warstwy JPA udostępniany warstwie [dao] pozostaje taki sam.

W niniejszym dokumencie przedstawiono przykłady JPA w różnych dziedzinach:

  • Na początek przyjrzymy się mostkowi relacyjno-obiektowemu, który tworzy warstwa ORM. Zostanie on utworzony przy użyciu adnotacji Java 5 dla baz danych, w których występują relacje między tabelami typu:
    • jeden do jednego
    • jeden do wielu
    • wiele do wielu

Aby zilustrować ten obszar, stworzymy następujące architektury testowe:

Nasze programy testowe będą aplikacjami konsolowymi, które będą bezpośrednio komunikować się z warstwą JPA. Przy tej okazji zapoznamy się z głównymi metodami warstwy JPA. Będziemy pracować w środowisku zwanym „Java SE” (Standard Edition). JPA działa zarówno w środowisku Java SE, jak i Java EE5 (Enterprise Edition).

  • Gdy opanujemy zarówno konfigurację mostu relacyjno-obiektowego, jak i korzystanie z metod warstwy JPA, powrócimy do bardziej klasycznej architektury wielowarstwowej:

Dostęp do warstwy [JPA] będzie realizowany poprzez architekturę dwuwarstwową [metier] i [dao]. Do połączenia tych warstw zostanie wykorzystany framework Spring [7], a następnie kontener EJB3 z JBoss.

Jak wspomniano powyżej, JPA jest dostępny w środowiskach SE i EE5. Środowisko Java EE5 zapewnia wiele usług w zakresie dostępu do danych trwałych, w szczególności pule połączeń, menedżery transakcji itp. Dla programisty korzystne może być wykorzystanie tych usług. Środowisko Java EE5 nie jest jeszcze zbyt rozpowszechnione (maj 2007 r.). Obecnie można je znaleźć na serwerze aplikacji Sun Application Server 9.x (Glassfish). Serwer aplikacji jest zasadniczo serwerem aplikacji internetowych. Jeśli tworzy się samodzielną aplikację graficzną typu Swing, nie ma dostępu do środowiska EE i usług, które ono zapewnia. Stanowi to problem. Zaczynają pojawiać się środowiska „samodzielne” typu EE, c.a.d. które mogą być używane poza serwerem aplikacji. Tak jest w przypadku JBos i EJB3, z których będziemy korzystać w niniejszym dokumencie.

W środowisku EE5 warstwy są implementowane przez obiekty zwane EJB (Enterprise Java Bean). W poprzednich wersjach EE obiekty EJB (EJB, 2.x) były uważane za trudne do wdrożenia, testowania, a czasami także za mało wydajne. Rozróżnia się „entity” EJB2.x oraz „session” EJB2.x. Krótko mówiąc, „entity” typu EJB2.x jest odwzorowaniem wiersza tabeli bazy danych, a „session” typu EJB2.x jest obiektem wykorzystywanym do implementacji warstw [metier], [dao] w architekturze wielowarstwowej. Jednym z głównych zarzutów wobec warstw zaimplementowanych przy użyciu EJB jest to, że można z nich korzystać wyłącznie w ramach kontenerów EJB, usługi dostarczanej przez środowisko EE. Powoduje to problemy z testami jednostkowymi. Tak więc na powyższym schemacie testy jednostkowe warstw [metier] i [dao], zbudowanych przy użyciu EJB, wymagałyby uruchomienia serwera aplikacji, co jest dość uciążliwą operacją i nie zachęca programisty do częstego przeprowadzania testów.

Framework Spring powstał w odpowiedzi na złożoność środowisk EJB2. Spring zapewnia w środowisku SE znaczną liczbę usług, które zazwyczaj są dostarczane przez środowiska EE. Tak więc w interesującej nas tutaj części „Trwałość danych” Spring zapewnia pule połączeń i menedżery transakcji, których potrzebują aplikacje. Pojawienie się Springa sprzyjało rozwojowi kultury testów jednostkowych, których wdrożenie stało się nagle znacznie łatwiejsze. Spring umożliwia implementację warstw aplikacji za pomocą klasycznych obiektów Java (POJO, Plain Old/Ordinary Java Object), co pozwala na ich ponowne wykorzystanie w innym kontekście. Wreszcie, w dość przejrzysty sposób integruje wiele narzędzi innych producentów, w szczególności narzędzi do trwałości danych, takich jak Hibernate, Ibatis...

Java EE5 została zaprojektowana w celu uzupełnienia braków poprzedniej specyfikacji EE. EJB i 2.x stały się EJB3. Są to obiekty POJOs oznaczone adnotacjami, które sprawiają, że stają się one obiektami specjalnymi, gdy znajdują się w kontenerze EJB3. W tym kontenerze obiekt EJB3 będzie mógł korzystać z usług kontenera (pula połączeń, menedżer transakcji itp.). Poza kontenerem EJB3 obiekt EJB3 staje się zwykłym obiektem Java. Jego adnotacje EJB są ignorowane.

Powyżej przedstawiliśmy Spring oraz JBoss i EJB3 jako potencjalną infrastrukturę (framework) naszej architektury wielowarstwowej. To właśnie ta infrastruktura zapewni nam potrzebne usługi: pulę połączeń i menedżera transakcji.

  • W przypadku Springa warstwy będą zaimplementowane za pomocą komponentów POJOs. Będą one miały dostęp do usług Springa (pula połączeń, menedżer transakcji) poprzez wstrzykiwanie zależności do tych komponentów POJOs: podczas ich tworzenia Spring wstrzykuje do nich odniesienia do usług, których będą potrzebować.
  • JBoss EJB3 to kontener EJB, który może działać poza serwerem aplikacji. Jego zasada działania (z punktu widzenia programisty) jest analogiczna do tej opisanej dla Springa. Znajdziemy tu niewiele różnic.

  • Na zakończenie niniejszego dokumentu przedstawimy przykładową, prostą, ale reprezentatywną trójwarstwową aplikację internetową:

1.2. Références

[ref1]: Java Persistence with Hibernate, autorstwa Christiana Bauera i Gavina Kinga, wydawnictwo Manning.

[ref1] to dokument, który posłużył za podstawę poniższego tekstu. Jest to obszerna, licząca ponad 800 stron książka poświęcona wykorzystaniu Hibernate w dwóch różnych kontekstach: z ORM i bez niego. Korzystanie z Hibernate bez JPA jest bowiem nadal aktualne dla programistów korzystających z JDK w wersji 1.4 lub niższej, ponieważ JPA pojawił się dopiero wraz z wersją JDK 1.5.

Po przeczytaniu ponad trzech czwartych książki i przejrzeniu pozostałej części doszedłem do wniosku, że wszystko w tym dokumencie jest przydatne. Doświadczony użytkownik Hibernate powinien znać niemal wszystkie informacje zawarte na tych 800 stronach. Christian Bauer i Gavin King opisali wszystko wyczerpująco, ale rzadko zajmowali się sytuacjami, z którymi nigdy się nie spotkamy. Warto przeczytać całość. Książka została napisana w sposób dydaktyczny: widać w niej prawdziwą chęć wyjaśnienia wszystkich zagadnień. Fakt, że została napisana z myślą o wykorzystaniu Hibernate zarówno z JPA, jak i bez niego, stanowi utrudnienie dla tych, którzy interesują się tylko jedną z tych technologii. Na przykład autorzy opisują, na podstawie licznych przykładów, most relacyjno-obiektowy w obu kontekstach. Stosowane koncepcje są bardzo zbliżone, ponieważ JPA czerpie silną inspirację z Hibernate. Istnieją jednak pewne różnice. W rezultacie to, co jest prawdą w przypadku Hibernate, może nie mieć zastosowania w przypadku JPA, co ostatecznie powoduje dezorientację u czytelnika.

Autorzy przedstawiają przykłady aplikacji trójwarstwowych w kontekście kontenera EJB3. Nie wspominają o Springu. Na przykładzie zobaczymy jednak, że Spring jest prostszy w użyciu i ma bardziej kompleksowy zakres działania niż kontener JBoss EJB3 wykorzystywany w [ref1]. Niemniej jednak „Java Persistence with Hibernate” to doskonała książka, którą polecam ze względu na wszystkie podstawowe informacje na temat ORM, które można w niej znaleźć.

Korzystanie z ORM jest skomplikowane dla początkujących.

  • Aby skonfigurować most relacyjno-obiektowy, należy zrozumieć pewne pojęcia.
  • Istnieje pojęcie kontekstu trwałości wraz z pojęciami obiektów w stanie „trwałym”, „odłączonym” i „nowym”
  • istnieją mechanizmy związane z trwałością (transakcje, pule połączeń), zazwyczaj usługi dostarczane przez kontener
  • trzeba wprowadzić ustawienia dotyczące wydajności (pamięć podręczna drugiego poziomu)
  • ...

Przedstawimy te pojęcia na przykładach. Nie będziemy się zbytnio zagłębiać w teorię z nimi związaną. Naszym celem jest po prostu, za każdym razem, umożliwienie czytelnikowi zrozumienia przykładu i przyswojenia go sobie na tyle, by był w stanie samodzielnie wprowadzać w nim zmiany lub zastosować go w innym kontekście.

1.3. Wykorzystane narzędzia

W przykładach zawartych w niniejszym dokumencie wykorzystano następujące narzędzia. Niektóre z nich opisano w załącznikach (pobieranie, instalacja, konfiguracja, obsługa). W takich przypadkach podano numer akapitu i stronę.

  • JDK 1.6 (punkt 5.1)
  • IDE do programowania w Javie w środowisku Eclipse 3.2.2 (punkt 5.2)
  • wtyczka Eclipse WTP (pakiet narzędzi internetowych) (punkt 5.2.3)
  • wtyczka Eclipse SQL Explorer (punkt 5.2.6)
  • wtyczka Eclipse Hibernate Tools (punkt 5.2.5)
  • wtyczka Eclipse TestNG (punkt 5.2.4)
  • kontener serwletów Tomcat 5.5.23 (punkt 5.3)
  • SGBD Firebird 2.1 (punkt 5.4)
  • SGBD MySQL5 (punkt 5.5)
  • SGBD PosgreSQL (punkt 5.6)
  • SGBD Oracle 10g Express (punkt 5.7)
  • SGBD SQL Server 2005 Express (punkt 5.8)
  • SGBD HSQLDB (punkt 5.9)
  • SGBD Apache Derby (punkt 5.10)
  • Spring 2.1 (punkt 5.11)
  • kontener EJB3 z JBoss (punkt 5.12)

1.4. Pobieranie przykładów z pliku „

Na stronie internetowej niniejszego dokumentu omówione przykłady można pobrać w postaci pliku zip, który po rozpakowaniu tworzy następujący folder:

  • w formacie [1]: struktura katalogów przykładów
  • w pliku [2]: folder <annexes> zawiera elementy przedstawione w części ANNEXES, akapit 5. W szczególności folder <jdbc> zawiera sterowniki JDBC z pliku SGBD, wykorzystywane w przykładach z samouczka.
  • w [3]: folder <lib> zawiera 5 folderów z różnymi plikami archiwów .jar wykorzystywanymi w samouczku
  • w pliku [4]: folder <lib/divers> zawiera archiwa: - sterowników JDBC z pliku SGBD - narzędzia do testów jednostkowych [testNG] - narzędzia do logowania [log4j]
  • w [5]: archiwa implementacji JPA/Hibernate oraz narzędzi stron trzecich niezbędnych dla Hibernate
  • do [6]: archiwa implementacji JPA/Toplink
  • w [7]: archiwa Springa 2.x oraz narzędzi innych producentów niezbędnych dla Springa
  • w [8]: archiwa kontenera EJB3 z JBoss
  • w [9]: folder <hibernate> zawiera przykłady wykorzystujące warstwę trwałości JPA/Hibernate
  • w [10]: folder <hibernate/direct> zawiera przykłady, w których warstwa JPA jest wykorzystywana bezpośrednio z programem typu [Main].
  • w [11] i [12]: przykłady, w których warstwa JPA jest wykorzystywana za pośrednictwem warstw [metier] i [dao] w architekturze wielowarstwowej, co stanowi standardowy sposób działania. Usługi (pula połączeń, menedżer transakcji) wykorzystywane przez warstwy [metier] i [dao] są dostarczane albo przez Spring [11], albo przez JBoss, EJB3, [12].
  • w pliku [13]: folder <toplink> zawiera przykłady z folderu <hibernate> [9], ale tym razem z warstwą trwałości JPA/Toplink zamiast JPA/Hibernate. WW [13] nie ma folderu <jbossejb3>, ponieważ nie udało się uruchomić przykładu, w którym warstwa trwałości jest zapewniana przez Toplink, a usługi przez kontener EJB3 z JBoss.
  • W [14]: folder <web> zawiera trzy przykłady aplikacji internetowych z warstwą trwałości JPA:
  • [15]: przykład z wykorzystaniem Spring / JPA / Hibernate
  • [16]: ten sam przykład z wykorzystaniem Spring / JPA / Toplink
  • [17]: ten sam przykład z wykorzystaniem JBoss, EJB3 oraz JPA / Hibernate. Ten przykład nie działa, prawdopodobnie z powodu niewyjaśnionego problemu konfiguracyjnego. Został on jednak zamieszczony, aby czytelnik mógł się nim zająć i ewentualnie znaleźć rozwiązanie tego problemu.

W samouczku często odwołujemy się do tej struktury katalogów, zwłaszcza podczas testowania omawianych przykładów. Zachęcamy czytelnika do pobrania tych przykładów i zainstalowania ich. W dalszej części będziemy nazywać <przykłady> strukturę katalogów z przykładami opisaną powyżej.

1.5. Konfiguracja projektów „ ” w Eclipse dla przykładów

W przykładach wykorzystano biblioteki „użytkownika”. Są to archiwa .jar zebrane pod jedną nazwą. Po dołączeniu takiej biblioteki do pliku classpath projektu Java wszystkie zawarte w niej archiwa zostają włączone do ścieżki klasy. Zobaczmy, jak to zrobić w programie Eclipse:

  • w [1]: [Window / Preferences / Java / Buld Path / User Libraries]
  • w [2]: tworzymy nową bibliotekę
  • w [3]: nadajemy jej nazwę i zatwierdzamy
  • w [4]: wybieramy pliki JAR, które będą częścią biblioteki [jpa-divers]
  • w [5]: wybieramy wszystkie pliki JAR z folderu <przykłady>/lib/różne
  • w [6]: zdefiniowano bibliotekę użytkownika [jpa-divers]
  • w [7]: powtarzamy tę samą procedurę, aby utworzyć 4 kolejne biblioteki:
Biblioteka
Folder plików JAR biblioteki
jpa-hibernate
<przykłady>/lib/hibernate
jpa-toplink
<przykłady>/lib/toplink
jpa-spring
<przykłady>/lib/spring
jpa-jbossejb3
<przykłady>/lib/jbossejb3