Darmowy fragment publikacji:
Tytuł oryginału: Java Persistence with Hibernate, 2nd Edition
Tłumaczenie: Radosław Meryk
Projekt okładki: Studio Gravite / Olsztyn; Obarek, Pokoński, Pazdrijowski, Zaprucki
Materiały graficzne na okładce zostały wykorzystane za zgodą Shutterstock Images LLC.
ISBN: 978-83-283-2782-5
Original edition copyright © 2016 by Manning Publications Co.
All rights reserved.
Polish edition copyright © 2017 by HELION SA
All rights reserved.
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means,
electronic or mechanical, including photocopying, recording or by any information storage retrieval system,
without permission from the Publisher.
Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej
publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną,
fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje
naruszenie praw autorskich niniejszej publikacji.
Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich
właścicieli.
Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były
kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane
z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie
ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji
zawartych w książce.
Wydawnictwo HELION
ul. Kościuszki 1c, 44-100 GLIWICE
tel. 32 231 22 19, 32 230 98 63
e-mail: helion@helion.pl
WWW: http://helion.pl (księgarnia internetowa, katalog książek)
Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://helion.pl/user/opinie/javpe2
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.
Printed in Poland.
• Kup książkę
• Poleć książkę
• Oceń książkę
• Księgarnia internetowa
• Lubię to! » Nasza społeczność
Spis treści
Słowo wstępne do pierwszego wydania 17
Przedmowa 19
Podziękowania 21
O książce 23
O autorach 27
CZĘŚĆ I WPROWADZENIE W TEMATYKĘ ORM 29
Rozdział 1. Utrwalanie obiektowo-relacyjne 31
1.1. Co to jest utrwalanie? 32
1.1.1. Relacyjne bazy danych 33
1.1.2.
1.1.3. Korzystanie z języka SQL w Javie 35
Język SQL 34
1.2. Niedopasowanie paradygmatów 37
1.2.1.
1.2.2.
1.2.3.
1.2.4.
1.2.5.
Problem ziarnistości 38
Problem podtypów 40
Problem tożsamości 41
Problemy związane z asocjacjami 43
Problem poruszania się po danych 44
1.3. ORM i JPA 45
1.4.
Podsumowanie 47
Rozdział 2. Zaczynamy projekt 49
2.1. Wprowadzenie do frameworka Hibernate 49
2.2. Aplikacja „Witaj, świecie” z JPA 50
2.2.1. Konfigurowanie jednostki utrwalania 51
2.2.2.
2.2.3.
Pisanie klasy utrwalania 53
Zapisywanie i ładowanie komunikatów 54
2.3. Natywne mechanizmy konfiguracji frameworka Hibernate 57
2.4.
Podsumowanie 60
3.1.
Rozdział 3. Modele dziedziny i metadane 61
Przykładowa aplikacja CaveatEmptor 62
3.1.1. Architektura warstwowa 62
3.1.2. Analiza dziedziny biznesowej 64
3.1.3. Model dziedziny aplikacji CaveatEmptor 65
Poleć książkęKup książkę8
Spis treści
3.2.
Implementacja modelu dziedziny 66
3.2.1. Rozwiązanie problemu wyciekania obszarów zainteresowania 67
3.2.2.
3.2.3.
3.2.4.
Przezroczyste i zautomatyzowane utrwalanie 68
Pisanie klas zdolnych do utrwalania 69
Implementacja asocjacji POJO 73
3.3. Metadane modelu dziedziny 77
3.3.1. Metadane bazujące na adnotacjach 78
Stosowanie reguł Bean Validation 80
3.3.2.
3.3.3.
Pobieranie metadanych z zewnętrznych plików XML 83
3.3.4. Dostęp do metadanych w fazie działania aplikacji 87
Podsumowanie 90
3.4.
CZĘŚĆ II STRATEGIE MAPOWANIA 91
Rozdział 4. Mapowanie klas utrwalania 93
4.1. Encje i typy wartości 93
4.1.1. Drobnoziarniste modele dziedziny 94
4.1.2. Definiowanie pojęć aplikacji 94
4.1.3. Odróżnianie encji od typów wartości 96
4.2. Mapowanie encji z tożsamością 97
Tożsamość a równość w Javie 98
Pierwsza klasa encji i mapowanie 98
4.2.1.
4.2.2.
4.2.3. Wybieranie klucza głównego 100
4.2.4. Konfigurowanie generatorów kluczy 101
4.2.5.
Strategie generatorów identyfikatorów 104
4.3. Opcje mapowania encji 108
Zarządzanie nazwami 108
4.3.1.
4.3.2. Dynamiczne generowanie SQL 111
4.3.3. Encje niezmienne 112
4.3.4. Mapowanie encji na podzapytanie 113
Podsumowanie 114
4.4.
Rozdział 5. Mapowanie typów wartości 117
5.1. Mapowanie prostych właściwości 118
Przesłanianie domyślnego zachowania dla właściwości o typach prostych 119
Personalizacja dostępu do właściwości 120
5.1.1.
5.1.2.
5.1.3. Używanie właściwości wyprowadzonych 122
5.1.4.
5.1.5. Wygenerowane i domyślne wartości właściwości 124
5.1.6. Właściwości opisujące czas 125
5.1.7. Mapowanie typów wyliczeniowych 126
Transformacje wartości kolumn 123
5.2. Mapowanie komponentów osadzanych 126
Schemat bazy danych 127
Przystosowanie klas do osadzania 127
Przesłanianie osadzonych atrybutów 131
5.2.1.
5.2.2.
5.2.3.
5.2.4. Mapowanie zagnieżdżonych komponentów osadzanych 132
Poleć książkęKup książkęSpis treści
9
5.3. Mapowanie typów Java i SQL za pomocą konwerterów 134
Typy wbudowane 134
Tworzenie własnych konwerterów JPA 140
5.3.1.
5.3.2.
5.3.3. Rozszerzanie frameworka Hibernate za pomocą typów użytkownika 147
Podsumowanie 154
5.4.
Rozdział 6. Mapowanie dla dziedziczenia 155
Jedna tabela na konkretną klasę. Niejawna obsługa polimorfizmu 156
Jedna tabela na konkretną klasę oraz unie 158
Jedna tabela na hierarchię klas 160
Jedna tabela na podklasę oraz złączenia 164
6.1.
6.2.
6.3.
6.4.
6.5. Mieszane strategie dziedziczenia 167
6.6. Dziedziczenie klas osadzanych 169
6.7. Wybór strategii 172
6.8. Asocjacje polimorficzne 173
Polimorficzne asocjacje wiele-do-jednego 173
Polimorficzne kolekcje 176
6.8.1.
6.8.2.
Podsumowanie 177
6.9.
Rozdział 7. Mapowanie kolekcji i asocjacje pomiędzy encjami 179
7.1.
Schemat bazy danych 180
Tworzenie i mapowanie właściwości będących kolekcjami 180
Zbiory, kolekcje bag, listy i mapy typów wartości 180
7.1.1.
7.1.2.
7.1.3. Wybór interfejsu kolekcji 182
7.1.4. Mapowanie zbioru 184
7.1.5. Mapowanie kolekcji bag identyfikatorów 185
7.1.6. Mapowanie list 186
7.1.7. Mapowanie mapy 187
7.1.8. Kolekcje posortowane i uporządkowane 188
7.2. Kolekcje komponentów 191
7.2.1. Równość egzemplarzy komponentu 192
7.2.2. Kolekcja Set komponentów 194
7.2.3. Kolekcja bag komponentów 196
7.2.4. Mapa wartości komponentów 197
7.2.5. Komponenty jako klucze mapy 198
7.2.6. Kolekcje w komponencie osadzanym 199
7.3. Mapowanie asocjacji encji 200
7.3.1. Najprostsza możliwa asocjacja 201
7.3.2. Definiowanie asocjacji dwukierunkowych 202
7.3.3. Kaskadowe zmiany stanu 204
Podsumowanie 211
7.4.
Poleć książkęKup książkę10
Spis treści
Rozdział 8. Zaawansowane mapowanie asocjacji pomiędzy encjami 213
8.1. Asocjacje jeden-do-jednego 214
8.1.1. Współdzielenie klucza głównego 214
8.1.2. Generator kluczy obcych i głównych 217
8.1.3. Wykorzystanie kolumny złączenia klucza obcego 220
8.1.4. Korzystanie z tabeli złączenia 221
8.2. Asocjacje jeden-do-wielu 224
Jednokierunkowe i dwukierunkowe mapowania list 226
8.2.1. Kolekcje bag jeden-do-wielu 224
8.2.2.
8.2.3. Opcjonalna asocjacja jeden-do-wielu z tabelą złączenia 228
8.2.4. Asocjacje jeden-do-wielu w klasach osadzanych 230
8.3. Asocjacje wiele-do-wielu i asocjacje potrójne 232
Jednokierunkowe i dwukierunkowe asocjacje wiele-do-wielu 232
8.3.1.
8.3.2. Asocjacja wiele-do-wielu z pośrednią encją 234
8.3.3. Asocjacje trójelementowe z komponentami 238
8.4. Asocjacje pomiędzy encjami z wykorzystaniem kolekcji Map 241
8.4.1. Relacje jeden-do-wielu z kluczem w postaci właściwości 241
8.4.2. Relacje trójczłonowe klucz-wartość 243
Podsumowanie 244
8.5.
Rozdział 9. Schematy złożone i odziedziczone 245
9.1. Ulepszanie schematu bazy danych 246
9.1.1. Dodawanie pomocniczych obiektów bazy danych 247
9.1.2. Ograniczenia SQL 250
9.1.3.
Tworzenie indeksów 257
9.2. Obsługa kluczy odziedziczonych 258
9.2.1. Mapowanie naturalnych kluczy głównych 258
9.2.2. Mapowanie złożonych kluczy głównych 259
9.2.3. Klucze obce w złożonych kluczach głównych 261
9.2.4. Klucze obce odwołująe się do złożonych kluczy głównych 264
9.2.5. Klucze obce odwołujące się do pól niebędących kluczami głównymi 265
9.3. Mapowanie właściwości do tabel pomocniczych 266
9.4.
Podsumowanie 268
CZĘŚĆ III TRANSAKCYJNE PRZETWARZANIE DANYCH 269
Rozdział 10. Zarządzanie danymi 271
10.1. Cykl życia utrwalania 272
10.1.1. Stany egzemplarza encji 273
10.1.2. Kontekst utrwalania 274
10.2. Interfejs EntityManager 276
10.2.1. Kanoniczna jednostka pracy 276
10.2.2. Utrwalanie danych 278
10.2.3. Pobieranie i modyfikowanie trwałych danych 279
10.2.4. Pobieranie referencji 281
10.2.5. Przełączanie danych do stanu przejściowego 282
10.2.6. Odświeżanie danych 284
Poleć książkęKup książkęSpis treści
11
10.2.7. Replikowanie danych 284
10.2.8. Buforowanie w kontekście utrwalania 285
10.2.9. Synchronizowanie kontekstu utrwalania 287
10.3. Praca z encjami w stanie odłączonym 288
Implementacja metody równości 291
10.3.1. Tożsamość odłączonych egzemplarzy 289
10.3.2.
10.3.3. Odłączanie egzemplarzy encji 294
10.3.4. Scalanie egzemplarzy encji 295
10.4. Podsumowanie 297
Rozdział 11. Transakcje i współbieżność 299
11.1. Podstawowe wiadomości o transakcjach 300
11.1.1. Cechy ACID 300
11.1.2. Transakcje bazodanowe i systemowe 301
11.1.3. Transakcje programowe z JTA 301
11.1.4. Obsługa wyjątków 303
11.1.5. Deklaratywne rozgraniczanie transakcji 306
11.2. Zarządzanie współbieżnym dostępem 306
11.2.1. Współbieżność na poziomie bazy danych 307
11.2.2. Optymistyczne zarządzanie współbieżnością 312
11.2.3.
11.2.4. Unikanie zakleszczeń 324
Jawne pesymistyczne blokady 320
11.3. Dostęp do danych na zewnątrz transakcji 325
11.3.1. Czytanie danych w trybie autozatwierdzania 326
11.3.2. Kolejkowanie modyfikacji 328
11.4. Podsumowanie 329
Rozdział 12. Plany pobierania, strategie i profile 331
12.1. Ładowanie leniwe i zachłanne 332
12.1.1. Obiekty proxy encji 333
12.1.2. Leniwe ładowanie trwałych kolekcji 337
12.1.3. Leniwe ładowanie z przechwytywaniem 339
12.1.4. Zachłanne ładowanie asocjacji i kolekcji 342
12.2. Wybór strategii pobierania 344
12.2.1. Problem n+1 instrukcji SELECT 345
12.2.2. Problem iloczynu kartezjańskiego 346
12.2.3. Pobieranie danych partiami z wyprzedzeniem 349
12.2.4. Pobieranie kolekcji z wyprzedzeniem z wykorzystaniem podzapytań 351
12.2.5. Pobieranie zachłanne z wieloma instrukcjami SELECT 352
12.2.6. Dynamiczne pobieranie zachłanne 353
12.3. Korzystanie z profili pobierania 355
12.3.1. Deklarowanie profili pobierania Hibernate 356
12.3.2. Korzystanie z grafów encji 357
12.4. Podsumowanie 361
Poleć książkęKup książkę12
Spis treści
Rozdział 13. Filtrowanie danych 363
13.1. Kaskadowe przejścia stanu 364
13.1.1. Dostępne opcje kaskadowe 365
13.1.2. Przechodnie odłączanie i scalanie 366
13.1.3. Kaskadowe odświeżanie 368
13.1.4. Kaskadowe replikacje 370
13.1.5. Włączanie globalnej opcji przechodniego utrwalania 371
13.2. Nasłuchiwanie i przechwytywanie zdarzeń 372
13.2.1. Obserwatory zdarzeń i wywołania zwrotne JPA 372
13.2.2.
13.2.3. Rdzeń systemu obsługi zdarzeń 380
Implementacja interceptorów Hibernate 376
13.3. Audyt i wersjonowanie z wykorzystaniem Hibernate Envers 382
13.3.1. Aktywacja rejestrowania audytu 382
13.3.2. Tworzenie śladu audytu 384
13.3.3. Szukanie wersji 385
13.3.4. Dostęp do historycznych danych 386
13.4. Dynamiczne filtry danych 389
13.4.1. Definiowanie dynamicznych filtrów 390
13.4.2. Stosowanie filtra 390
13.4.3. Włączanie filtra 391
13.4.4. Filtrowanie dostępu do kolekcji 392
13.5. Podsumowanie 393
CZĘŚĆ IV PISANIE ZAPYTAŃ 395
Rozdział 14. Tworzenie i uruchamianie zapytań 397
14.1. Tworzenie zapytań 398
Interfejsy zapytań JPA 398
14.1.1.
14.1.2. Wyniki zapytania z określonym typem danych 401
14.1.3.
Interfejsy zapytań frameworka Hibernate 402
14.2. Przygotowywanie zapytań 403
14.2.1. Zabezpieczenie przed atakami wstrzykiwania SQL 403
14.2.2. Wiązanie parametrów nazwanych 404
14.2.3. Korzystanie z parametrów pozycyjnych 406
14.2.4. Stronicowanie dużych zbiorów wyników 406
14.3. Uruchamianie zapytań 408
14.3.1. Wyszczególnianie wszystkich wyników 408
14.3.2. Pobieranie pojedynczego wyniku 408
14.3.3. Przewijanie z wykorzystaniem kursorów bazy danych 409
14.3.4.
Iterowanie po wynikach 411
14.4. Nazywanie i eksternalizacja zapytań 412
14.4.1. Wywoływanie zapytania przez nazwę 412
14.4.2. Definiowanie zapytań w metadanych XML 413
14.4.3. Definiowanie zapytań z adnotacjami 414
14.4.4. Programowe definiowanie nazwanych zapytań 415
14.5. Podpowiedzi do zapytań 416
14.5.1. Ustawianie limitu czasu 417
14.5.2. Ustawianie trybu synchronizacji 417
Poleć książkęKup książkęSpis treści
13
14.5.3. Ustawianie trybu tylko do odczytu 418
14.5.4. Ustawianie rozmiaru pobierania 418
14.5.5. Ustawianie komentarza SQL 419
14.5.6. Podpowiedzi nazwanych zapytań 419
14.6. Podsumowanie 421
Rozdział 15. Języki zapytań 423
15.1. Selekcja 424
15.1.1. Przypisywanie aliasów i rdzeni zapytań 425
15.1.2. Zapytania polimorficzne 426
15.2. Ograniczenia 427
15.2.1. Wyrażenia porównań 428
15.2.2. Wyrażenia z kolekcjami 432
15.2.3. Wywoływanie funkcji 433
15.2.4. Sortowanie wyników zapytania 436
15.3. Rzutowanie 437
15.3.1. Rzutowanie encji i wartości skalarnych 437
15.3.2. Dynamiczne tworzenie egzemplarzy 439
15.3.3. Niepowtarzalność wyników 440
15.3.4. Wywoływanie funkcji w projekcjach 441
15.3.5. Funkcje agregacji 443
15.3.6. Grupowanie 445
15.4. Złączenia 446
15.4.1. Złączenia w SQL 447
15.4.2. Opcje złączenia w JPA 449
15.4.3. Niejawne złączenia asocjacyjne 449
15.4.4. Złączenia jawne 451
15.4.5. Dynamiczne pobieranie ze złączeniami 453
15.4.6. Złączenia teta 456
15.4.7. Porównywanie identyfikatorów 457
15.5. Podzapytania 459
15.5.1. Zagnieżdżanie skorelowane i nieskorelowane 460
15.5.2. Kwantyfikacja 461
15.6. Podsumowanie 462
Rozdział 16. Zaawansowane opcje zapytań 465
16.1. Transformacje wyników zapytań 466
16.1.1. Zwracanie listy list 466
16.1.2. Zwracanie listy map 467
16.1.3. Mapowanie aliasów na właściwości JavaBean 468
16.1.4. Pisanie własnej implementacji klasy ResultTransformer 469
16.2. Filtrowanie kolekcji 470
16.3. API Criteria frameworka Hibernate 473
16.3.1. Selekcja i porządkowanie 473
16.3.2. Ograniczanie 474
16.3.3. Rzutowanie i agregacja 475
Poleć książkęKup książkę14
Spis treści
16.3.4. Złączenia 477
16.3.5. Podzapytania 478
16.3.6. Zapytania przez przykład 479
16.4. Podsumowanie 482
Rozdział 17. Dostosowywanie SQL 483
17.1. Korzystanie z trybu JDBC 484
17.2. Mapowanie wyników zapytania SQL 486
17.2.1. Rzutowanie z zapytaniami SQL 487
17.2.2. Mapowanie na klasy encji 488
17.2.3. Dostosowywanie mapowania wyników 490
17.2.4. Eksternalizacja natywnych zapytań 502
17.3. Dostosowywanie operacji CRUD 505
17.3.1. Własne mechanizmy ładujące 505
17.3.2. Personalizacja operacji tworzenia, aktualizowania i usuwania
egzemplarzy encji 507
17.3.3. Personalizacja operacji na kolekcjach 509
17.3.4. Zachłanne pobieranie we własnych mechanizmach ładujących 511
17.4. Wywoływanie procedur składowanych 513
17.4.1. Zwracanie zbioru wyników 515
17.4.2. Zwracanie wielu zbiorów wyników oraz liczników aktualizacji 516
17.4.3. Ustawianie parametrów wejściowych i wyjściowych 518
17.4.4. Zwracanie kursora 521
17.5. Wykorzystywanie procedur składowanych do operacji CRUD 522
17.5.1. Spersonalizowany mechanizm ładujący z procedurą 523
17.5.2. Procedury dla operacji CUD 524
17.6. Podsumowanie 526
CZĘŚĆ V BUDOWANIE APLIKACJI 527
Rozdział 18. Projektowanie aplikacji klient-serwer 529
18.1. Tworzenie warstwy utrwalania 530
18.1.1. Generyczny wzorzec obiektu dostępu do danych 532
18.1.2.
18.1.3.
18.1.4. Testowanie warstwy utrwalania 538
Implementacja generycznego interfejsu 533
Implementacja obiektów DAO encji 536
18.2. Budowa serwera bezstanowego 540
18.2.1. Edycja przedmiotu aukcji 540
18.2.2. Składanie oferty 543
18.2.3. Analiza aplikacji bezstanowej 546
18.3. Budowanie serwera stateful 548
18.3.1. Edycja przedmiotu aukcji 549
18.3.2. Analiza aplikacji stateful 554
18.4. Podsumowanie 556
Poleć książkęKup książkęSpis treści
15
Rozdział 19. Budowanie aplikacji webowych 557
19.1. Integracja JPA z CDI 558
19.1.1. Tworzenie obiektu EntityManager 558
19.1.2. Łączenie obiektu EntityManager z transakcjami 560
19.1.3. Wstrzykiwanie obiektu EntityManager 560
19.2. Stronicowanie i sortowanie danych 562
19.2.1. Stronicowanie na bazie przesunięć a stronicowanie przez przeszukiwanie 563
19.2.2. Stronicowanie w warstwie utrwalania 565
19.2.3. Odpytywanie strona po stronie 571
19.3. Budowanie aplikacji JSF 572
19.3.1. Usługi o zasięgu żądania 572
19.3.2. Usługi o zasięgu konwersacji 576
19.4. Serializacja danych modelu dziedziny 585
19.4.1. Pisanie usługi JAX-RS 585
19.4.2. Stosowanie mapowań JAXB 587
19.4.3. Serializacja obiektów proxy frameworka Hibernate 589
19.5. Podsumowanie 593
Rozdział 20. Skalowanie Hibernate 595
20.1. Przetwarzanie masowe i wsadowe 596
Instrukcje masowe w JPQL i API kryteriów 596
20.1.1.
20.1.2. Masowe instrukcje w SQL 601
20.1.3. Przetwarzanie wsadowe 602
20.1.4.
Interfejs StatelessSession frameworka Hibernate 606
20.2. Buforowanie danych 608
20.2.1. Architektura współdzielonej pamięci podręcznej frameworka Hibernate 609
20.2.2. Konfigurowanie współdzielonej pamięci podręcznej 614
20.2.3. Buforowanie encji i kolekcji 616
20.2.4. Testowanie współdzielonej pamięci podręcznej 619
20.2.5. Ustawianie trybów pamięci podręcznej 622
20.2.6. Zarządzanie współdzieloną pamięcią podręczną 623
20.2.7. Pamięć podręczna wyników zapytania 624
20.3. Podsumowanie 627
Bibliografia 629
Skorowidz 631
Poleć książkęKup książkę16
Spis treści
Poleć książkęKup książkęMapowanie klas utrwalania
W tym rozdziale:
omówienie encji i typów wartości;
mapowanie klas encji z tożsamością;
zarządzanie opcjami mapowania na poziomie encji.
W tym rozdziale zaprezentujemy podstawowe opcje mapowania i wyjaśnimy sposób
mapowania klas encji na tabele SQL. Pokażemy i omówimy sposoby obsługi identyfi-
katorów baz danych i kluczy głównych oraz użycia różnych innych ustawień metadanych
pozwalających na spersonalizowanie tego, jak framework Hibernate ładuje i magazynuje
egzemplarze klas modelu dziedziny. We wszystkich przykładach mapowania wykorzy-
stamy adnotacje JPA. Najpierw jednak zdefiniujemy podstawowe rozgraniczenie pomię-
dzy encjami a typami wartości i wyjaśnimy, w jaki sposób powinniśmy podejść do
mapowania obiektowo-relacyjnego modelu dziedziny.
Najważniejsza nowa własność w JPA 2
Za pomocą elementu delimited-identifiers w pliku konfiguracyjnym persistence.xml
można włączyć globalne „unieszkodliwianie” wszystkich nazw w generowanych instruk-
cjach SQL.
4.1. Encje i typy wartości
Analizując model dziedziny, można zauważyć różnice pomiędzy klasami: niektóre typy
wydają się ważniejsze od innych — reprezentują obiekty biznesowe pierwszej klasy
(termin obiekt został tutaj użyty w jego podstawowym znaczeniu). Przykładem są klasy
Poleć książkęKup książkę94
ROZDZIAŁ 4. Mapowanie klas utrwalania
Item, Category i User: są to encje świata rzeczywistego, dla których próbujemy stworzyć
reprezentacje (wróć do rysunku 3.3, na którym pokazano przykładowy model dzie-
dziny). Inne typy występujące w modelu dziedziny, takie jak Address, String i Integer,
wydają się mniej ważne. W tym podrozdziale opowiemy, co to znaczy używać drobno-
ziarnistych modeli dziedziny oraz w jaki sposób odróżnić encje od typów wartości.
4.1.1. Drobnoziarniste modele dziedziny
Głównym celem frameworka Hibernate jest wsparcie dla drobnoziarnistych i bogatych
modeli dziedziny. Jest to jeden z powodów, dla których korzystamy z obiektów POJO.
W uproszczeniu określenie drobnoziarnisty model oznacza model, w którym jest więcej
klas niż tabel.
Na przykład użytkownik w modelu dziedziny może mieć adres domowy. W bazie
danych może występować pojedyncza tabela USERS z kolumnami HOME_STREET, HOME_CITY
i HOME_ZIPCODE (pamiętasz problem typów SQL, który omawialiśmy w punkcie 1.2.1?).
W modelu dziedziny moglibyśmy użyć tego samego podejścia — zaprezentować
adres w postaci trzech właściwości tekstowych klasy User. Jednak znacznie lepsze jest
zamodelowanie adresu z wykorzystaniem klasy Address, gdzie w klasie User występuje
właściwość homeAddress. Taki model dziedziny gwarantuje ulepszoną spójność i łatwiej-
sze ponowne użycie kodu. Ponadto jest bardziej zrozumiały niż SQL z nieelastycznymi
systemami typów.
Specyfikacja JPA podkreśla przydatność drobnoziarnistych klas do implementacji
bezpieczeństwa typów i zachowań. Wiele osób na przykład modeluje adres e-mail jako
tekstową właściwość klasy User. Bardziej zaawansowanym podejściem jest zdefiniowa-
nie klasy EmailAddress, która dodaje bardziej wysokopoziomową semantykę i zacho-
wanie — klasa ta może dostarczać metodę prepareMail(), jednak nie powinna zawierać
metody sendMail(), ponieważ nie chcemy, aby klasy modelu dziedziny zależały od
podsystemu pocztowego.
Problem ziarnistości prowadzi do rozróżnienia pomiędzy klasami, które w kontekście
mechanizmów ORM ma centralne znaczenie. W Javie wszystkie klasy są równe —
wszystkie egzemplarze mają własny identyfikator i cykl życia. Gdy wprowadzimy utrwa-
lanie, to może się zdarzyć, że niektóre egzemplarze nie będą mieć własnych identyfi-
katorów i cyklu życia, lecz będą zależeć od innych. Spróbujmy przeanalizować przykład.
4.1.2. Definiowanie pojęć aplikacji
Dwie osoby mieszkają w tym samym domu i obie mają zarejestrowane konta użyt-
kownika systemu CaveatEmptor. Niechaj będą to Jan i Janina.
Każde konto reprezentuje egzemplarz klasy User. Ponieważ chcemy ładować, zapi-
sywać i usuwać egzemplarze klasy User niezależnie do siebie, to User jest klasą encji,
a nie typem wartości. Wyszukiwanie klas encji jest łatwe.
Klasa User ma właściwość homeAddress. Jest to asocjacja z klasą Address. Czy oba
egzemplarze klasy User mają w fazie działania aplikacji referencje do tego samego egzem-
plarza klasy Address, czy też każdy egzemplarz klasy User ma referencję do własnego
egzemplarza klasy Address? Czy ma znaczenie to, że Jan i Janina mieszkają w tym
samym domu?
Poleć książkęKup książkę4.1. Encje i typy wartości
95
Na rysunku 4.1 pokazano sytuację, w której dwa egzemplarze klasy User współdzielą
egzemplarz klasy Address (to jest diagram obiektów UML, a nie diagram klas). Jeżeli
obiekt klasy Address ma wspierać współdzielenie referencji w fazie działania, to jest to
typ encji. Egzemplarz klasy Address ma własny cykl życia. Nie można go usunąć, gdy
Jan usunie swoje konto User. Janina w dalszym ciągu będzie korzystać z referencji do
tego egzemplarza klasy Address.
Rysunek 4.1. Dwa egzemplarze
klasy User mają referencje
do jednego egzemplarza klasy
Address
Przyjrzyjmy się teraz modelowi alternatywnemu, w którym każdy obiekt klasy User
dysponuje referencją do własnego egzemplarza homeAddress — tak jak pokazano na
rysunku 4.2. W tym przypadku można uzależnić egzemplarz klasy Address od egzemplarza
klasy User: zdefiniujemy go jako typ wartości. Gdy Jan usunie swoje konto User, można
bezpiecznie usunąć związany z nim egzemplarz klasy Address. Ta referencja nie będzie
występować w egzemplarzu żadnej innej klasy.
Rysunek 4.2. Dwa egzemplarze
klasy User, z których każdy
ma własny, zależny egzemplarz
klasy Address
Wprowadzamy zatem następujące, zasadnicze rozróżnienie:
Możemy pobrać egzemplarz typu encji, używając jego tożsamości utrwalania: na
przykład egzemplarza User, Item albo Category. Referencja do egzemplarza encji
(wskaźnika w JVM) jest utrwalana jako referencja w bazie danych (wartość
z ograniczeniem klucza obcego). Egzemplarz encji ma własny cykl życia. Może
istnieć niezależnie od dowolnej innej encji. Wybrane klasy modelu dziedziny
mapujemy jako typy encji.
Egzemplarz typu wartości nie ma właściwości identyfikatora utrwalania. Należy
do egzemplarza encji. Jego cykl życia jest powiązany z egzemplarzem encji, który
jest jego właścicielem. Egzemplarz typu wartości nie wspiera współdzielonych
referencji. Najbardziej oczywistymi typami wartości są wszystkie klasy zdefi-
niowane w JDK, takie jak String, Integer, a nawet typy proste. Można także
mapować jako typy wartości klasy należące do naszego własnego modelu dzie-
dziny: na przykład Address i MonetaryAmount.
Poleć książkęKup książkę96
ROZDZIAŁ 4. Mapowanie klas utrwalania
W specyfikacji JPA można znaleźć takie samo pojęcie. Jednak typy wartości w specyfi-
kacji JPA są nazywane prostymi typami właściwości albo klasami osadzanymi (embed-
dable classes). Do tej tematyki powrócimy w następnym rozdziale. Najpierw skoncen-
trujemy się na encjach.
Identyfikowanie encji i typów wartości w modelu dziedziny nie jest zadaniem reali-
zowanym ad hoc, ale lecz zgodnie z konkretną procedurą.
4.1.3. Odróżnianie encji od typów wartości
Do diagramów klas UML można dodać informację na temat stereotypu (mechanizm
rozszerzeń UML). Dzięki niemu możliwe jest natychmiastowe rozpoznanie encji
i typów wartości. Ta praktyka zmusza także, aby myśleć o zastosowaniu tego rozróżnienia
dla wszystkich klas. Jest to pierwszy krok na drodze do optymalnego mapowania oraz
wydajnie działającej warstwy utrwalania. Przykład zaprezentowano na rysunku 4.3.
Rysunek 4.3.
Stereotypy dla encji
i typów wartości
na diagramach UML
Klasy Item i User są oczywistymi encjami. Każda ma własną tożsamość, ich egzemplarze
zawierają referencje z wielu innych egzemplarzy (referencje współdzielone) i mają
niezależne cykle życia.
Oznaczenie klasy Address jako typu wartości również jest łatwe: pojedynczy egzem-
plarz klasy User odwołuje się do konkretnego egzemplarza klasy Address. Wiemy to,
ponieważ asocjację utworzono jako kompozycję, przy czym egzemplarzowi klasy User
została powierzona pełna odpowiedzialność za cykl życia egzemplarza klasy Address.
Z tego powodu do egzemplarzy klasy Address nie mogą odwoływać się inne obiekty.
Egzemplarze te nie potrzebują też własnej tożsamości.
Problem może stworzyć klasa Bid. W modelowaniu obiektowym jest ona oznaczana
jako kompozycja (asocjacja pomiędzy egzemplarzem klasy Item i Bid oznaczona rombem).
Egzemplarz Item jest zatem właścicielem jego egzemplarzy klasy Bid i zawiera zbiór
referencji. Na pierwszy rzut oka wydaje się to sensowne, ponieważ oferty w systemie
aukcji są bezużyteczne w przypadku, gdy towar, na który zostały złożone, jest niedo-
stępny.
Co jednak zrobić, jeśli rozszerzenie modelu dziedziny w przyszłości będzie wymagać
kolekcji User#bids, zawierającej wszystkie oferty złożone przez konkretnego użytkow-
nika? W tym momencie asocjacja pomiędzy egzemplarzami Bid i User jest jednokie-
runkowa; egzemplarz klasy Bid zawiera referencję bidder. Co by było, gdyby była ona
dwukierunkowa?
W tym przypadku bylibyśmy zmuszeni do obsługi możliwego współdzielenia refe-
rencji z egzemplarzami klasy Bid, więc klasa Bid musiałaby być encją. Ma zależny
cykl życia, ale musiałaby mieć też własną tożsamość w celu wsparcia dla (przyszłych)
współdzielonych referencji.
Poleć książkęKup książkę4.2. Mapowanie encji z tożsamością
97
Z takim mieszanym zachowaniem można się często spotkać. W takiej sytuacji pierw-
szą reakcją powinno być przekształcenie wszystkich tego rodzaju klas na typy wartości
i promowanie ich do encji tylko wtedy, kiedy to jest absolutnie konieczne. Należy dążyć
do upraszczania asocjacji: na przykład utrwalane kolekcje często dodają złożoność bez
jakichkolwiek korzyści. Zamiast mapować kolekcje Item#bids i User#bids, możemy
napisać zapytania, aby uzyskać wszystkie oferty dla egzemplarza klasy Item oraz te,
które złożył określony użytkownik. Asocjacje na diagramie UML będą przebiegały od
egzemplarza Bid do egzemplarzy Item i User dokładnie w tym kierunku, a nie w kie-
runku przeciwnym. Dla klasy Bid zostanie użyty stereotyp Value type . Do tego
tematu wrócimy w rozdziale 7.
Następnie na podstawie diagramu modelu dziedziny zaimplementujemy obiekty
POJO dla wszystkich encji i typów wartości. Należy zatroszczyć się o trzy rzeczy:
Współdzielone referencje — podczas pisania klas POJO należy unikać współ-
dzielenia referencji do egzemplarzy typów wartości. Należy na przykład zadbać
o to, aby tylko jeden egzemplarz klasy User mógł odwoływać się do egzemplarza
klasy Address. Klasę Address można zaimplementować jako niezmienną, nie
definiując publicznej metody setUser() i wymuszając relację za pomocą publicz-
nego konstruktora pobierającego argument typu User. Oczywiście — zgodnie
z tym, o czym mówiliśmy w poprzednim rozdziale — nadal potrzebujemy kon-
struktora bez argumentów (prawdopodobnie protected), tak aby framework
Hibernate również mógł tworzyć egzemplarze.
Zależności cyklu życia — jeżeli egzemplarz klasy User zostanie usunięty, jego
zależność (egzemplarz klasy Address) także musi zostać usunięta. Metadane utrwa-
lania zawierają kaskadowe reguły dla wszystkich takich zależności, więc Hiber-
nate (lub baza danych) może zadbać o usunięcie przestarzałych egzemplarzy
klasy Address. Należy zaprojektować procedury aplikacji i interfejs użytkownika
tak, by respektowały takie zależności i ich oczekiwały — trzeba odpowiednio
napisać obiekty POJO modelu dziedziny.
Tożsamość — klasy encji wymagają właściwości identyfikatora w prawie wszyst-
kich przypadkach. Klasy typu wartości (i oczywiście klasy JDK takie jak String
i Integer) nie mają właściwości identyfikatora, ponieważ egzemplarze są iden-
tyfikowane za pośrednictwem encji właściciela.
Do referencji, asocjacji i reguł cyklu życia powrócimy przy okazji omawiania bardziej
zaawansowanych odwzorowań w dalszych rozdziałach niniejszej książki. Następnym
tematem będzie tożsamość i właściwości identyfikatorów.
4.2. Mapowanie encji z tożsamością
Mapowanie encji z tożsamością wymaga zrozumienia pojęć tożsamości i równości
w Javie. Dopiero po omówieniu tych pojęć przejdziemy do przykładu klasy encji i jej
mapowania. W dalszej części rozdziału omówimy bardziej szczegółowe zagadnienia.
Opiszemy problemy wybierania klucza podstawowego, konfigurowania generatorów
Poleć książkęKup książkę98
ROZDZIAŁ 4. Mapowanie klas utrwalania
kluczy i na koniec przejdziemy do strategii generowania identyfikatorów. Zanim przej-
dziemy do omówienia pojęcia tożsamość bazy danych oraz sposobu zarządzania tożsa-
mością w specyfikacji JPA, wyjaśnimy różnicę pomiędzy tożsamością a równością
obiektów w Javie.
4.2.1. Tożsamość a równość w Javie
Deweloperzy Javy rozumieją różnicę między tożsamością a równością obiektów Javy.
Tożsamość obiektów (==) to pojęcie zdefiniowane przez maszynę wirtualną Javy. Dwie
referencje są identyczne, jeżeli wskazują na tę samą lokalizację w pamięci.
Z kolei równość obiektów, czasami nazywana też równoważnością, jest pojęciem
zdefiniowanym przez metodę equals() klas. Równoważność oznacza, że dwa różne
(nieidentyczne) egzemplarze mają tę samą wartość — ten sam stan. Dwa różne egzem-
plarze klasy String są równe, jeżeli reprezentują tę samą sekwencję znaków pomimo
tego, że każdy egzemplarz ma własne miejsce w przestrzeni pamięci maszyny wirtualnej
(z czytelnikami, którzy są ekspertami w Javie, musimy się zgodzić, że klasa String to
przypadek specjalny; załóżmy, że do tego samego stwierdzenia skorzystaliśmy z innej
klasy).
Utrwalanie komplikuje ten obraz. W przypadku utrwalania obiektowo-relacyjnego
egzemplarz utrwalania jest w pamięci reprezentacją konkretnego wiersza (albo wierszy)
tabeli (lub tabel) bazy danych. Wraz z tożsamością i równością w Javie zdefiniujemy
tożsamość bazy danych. Mamy teraz trzy metody rozróżniania referencji:
Obiekty są identyczne, jeżeli zajmują to samo miejsce w pamięci maszyny JVM.
Można to sprawdzić za pomocą operatora a==b. To pojęcie jest znane jako tożsa-
mość obiektów.
Obiekty są równe, jeżeli mają ten sam stan zgodnie z definicją w metodzie
a.equals(Object b). Klasy, które jawnie nie przesłaniają tej metody, dziedziczą
implementację zdefiniowaną w klasie java.lang.Object, która porównuje toż-
samość obiektów za pomocą operatora ==. To pojęcie jest znane jako równość
obiektów.
Obiekty zapisane w relacyjnej bazie danych są identyczne, jeżeli współdzielą tę
samą tabelę i wartość klucza głównego. To pojęcie, zmapowane do przestrzeni
Javy, jest znane jako tożsamość bazy danych.
Przyjrzyjmy się teraz, w jaki sposób tożsamość na poziomie bazy danych łączy się z toż-
samością obiektów oraz w jaki sposób można wyrazić tożsamość bazy danych w meta-
danych mapowania. W ramach przykładu stworzymy mapę encji należącej do modelu
dziedziny.
4.2.2. Pierwsza klasa encji i mapowanie
W poprzednim rozdziale nie powiedzieliśmy całej prawdy: sama adnotacja @Entity nie
wystarczy do zmapowania klasy utrwalania. Potrzebna jest również adnotacja @Id, tak
jak pokazaliśmy na poniższym listingu.
Poleć książkęKup książkę4.2. Mapowanie encji z tożsamością
99
Listing 4.1. Zmapowana encja Item z właściwością identyfikatora
ŚCIEŻKA: /model/src/main/java/org/jpwh/model/simple/Item.java
@Entity
public class Item {
@Id
@GeneratedValue(generator = ID_GENERATOR )
protected Long id;
public Long getId() {
return id;
}
}
Opcjonalne, ale przydatne
To najprostsza klasa encji oznaczona jako „zdolna do utrwalania” za pomocą adnotacji
@Entity oraz zawierająca mapowanie @Id dla właściwości identyfikatora bazy danych.
Domyślnie klasa jest mapowana do tabeli o nazwie ITEM w schemacie bazy danych.
Każda klasa encji musi mieć właściwość @Id. W ten sposób specyfikacja JPA udostęp-
nia tożsamość bazy danych do aplikacji. Nie pokazujemy właściwości identyfikatora
na naszych diagramach. Zakładamy, że każda klasa encji ma identyfikator. W naszych
przykładach właściwości identyfikatora zawsze nadajemy nazwę id. Jest to dobra prak-
tyka projektowa. Warto stosować tę samą nazwę właściwości identyfikatora dla wszyst-
kich klas encji modelu dziedziny. Jeśli nie wprowadzimy innych specyfikacji, to ta
właściwość będzie zmapowana do kolumny klucza głównego o nazwie ID tabeli ITEM
należącej do schematu bazy danych.
Podczas ładowania i zapisywania elementów, aby uzyskać dostęp do wartości właści-
wości identyfikatora, framework Hibernate skorzysta z pola, a nie z metod gettera lub
settera. Ponieważ adnotacja @Id dotyczy pola, framework Hibernate domyślnie zezwoli
na to, aby każde pole klasy było utrwalaną właściwością. W specyfikacji JPA istnieje
następująca reguła: jeżeli zdefiniowano adnotację @Id dla pola, to JPA będzie korzystał
z pól klasy bezpośrednio i domyślnie uzna wszystkie pola za część utrwalanego stanu.
Sposób przesłonięcia tej własności opisaliśmy w dalszej części tego rozdziału. Z naszych
doświadczeń wynika, że dostęp do pól jest zwykle najlepszym wyborem, ponieważ
pozostawia znacznie więcej swobody podczas projektowania metod dostępowych.
Czy należy zdefiniować (publiczną) metodę gettera dla właściwości identyfikatora?
W aplikacjach często wykorzystuje się identyfikatory bazy danych jako wygodne uchwyty
określonego egzemplarza — nawet poza warstwą utrwalania. Przykładowo w aplikacjach
webowych wyniki wyszukiwania są powszechnie wyświetlane w formie listy podsumo-
wań. Gdy użytkownik wybierze konkretny element, aplikacja może potrzebować pobra-
nia wybranej pozycji. Do tego celu powszechnie wykorzystuje się wyszukiwanie według
identyfikatora. Prawdopodobnie już korzystałeś z identyfikatorów w taki sposób —
nawet w tych aplikacjach, które bazują na JDBC.
Czy należy zdefiniować metodę settera? Wartości kluczy głównych nigdy się nie
zmieniają, dlatego nie należy pozwalać na modyfikowanie wartości właściwości identy-
fikatora. Framework Hibernate nie aktualizuje kolumny klucza głównego i nie należy
udostępniać publicznej metody settera dla identyfikatora encji.
Poleć książkęKup książkę100
ROZDZIAŁ 4. Mapowanie klas utrwalania
Typ Javy właściwości identyfikatora — w poprzednim przykładzie java.lang.Long —
zależy od typu kolumny klucza głównego tabeli ITEM oraz sposobu tworzenia wartości
kluczy. To odsyła nas do adnotacji @GeneratedValue i do kluczy głównych w ogóle.
4.2.3. Wybieranie klucza głównego
Identyfikator bazy danych encji jest mapowany na klucz główny jakiejś tabeli, dlatego
najpierw spróbujemy omówić tematykę kluczy głównych bez zwracania uwagi na
mapowanie. Spróbujmy cofnąć się o krok i zastanowić nad tym, w jaki sposób identyfi-
kujemy encje.
Klucz kandydat jest kolumną (albo zbiorem kolumn), którą można wykorzystać do
zidentyfikowania określonego wiersza w tabeli. Aby klucz kandydat mógł zostać kluczem
głównym, musi spełnić następujące wymagania:
Wartością kolumny klucza kandydata nigdy nie jest null. Nie można niczego
zidentyfikować za pomocą nieznanych danych, dlatego w modelu relacyjnym nie
występują wartości null. Niektóre produkty SQL pozwalają na definiowanie
(złożonych) kluczy głównych z kolumnami, które mogą przyjmować wartości null,
dlatego należy zachować ostrożność.
Wartość kolumny (kolumn) klucza kandydata jest unikatowa dla każdego wiersza.
Wartość kolumny (kolumn) klucza kandydata nigdy się nie zmienia. Jest niemu-
towalna.
Czy klucze główne muszą być niemutowalne?
Klucz kandydat, zgodnie z definicją modelu relacyjnego, musi być unikatowy i niereduko-
walny (żaden podzbiór atrybutów klucza nie może być niepowtarzalny). Poza koniecz-
nością spełnienia tych wymagań wybór klucza kandydata, który ma pełnić funkcję klucza
głównego, jest kwestią gustu. Framework Hibernate oczekuje jednak, aby klucz kandydat
używany w roli klucza głównego był niezmienny. Hibernate nie wspiera aktualizowania
wartości klucza głównego za pośrednictwem API. Przy próbie obejścia tego wymagania
możemy napotkać problemy z mechanizmami buforowania i sprawdzania aktualizacji
frameworka. Jeżeli schemat bazy danych jest oparty na kluczach głównych dających się
aktualizować (i być może używa ograniczeń ON UPDATE CASCADE), to trzeba zmodyfikować
ten schemat, aby mógł działać z Hibernate.
Jeżeli tabela ma tylko jeden identyfikujący atrybut, to z definicji staje się on kluczem
głównym. Jednak w przypadku konkretnej tabeli te funkcje może spełniać kilka kolumn
albo kombinacja kilku kolumn. Aby zdecydować o tym, który klucz główny dla tabeli
będzie najlepszy, należy dokonać wyboru spośród kluczy kandydatów. Klucze kandy-
datów niewybrane do roli klucza głównego należy zadeklarować w bazie danych jako
unikatowe, jeśli ich wartość jest rzeczywiście unikatowa (ale być może nie jest nie-
zmienna).
W wielu klasycznych modelach danych SQL używane są naturalne klucze główne.
Klucz naturalny to taki, który ma znaczenie biznesowe: jest atrybutem lub kombinacją
atrybutów unikatową ze względu na biznesową semantykę. Przykładami naturalnych
kluczy obcych jest numer ubezpieczenia społecznego w Stanach Zjednoczonych lub
numer PESEL w Polsce. Rozpoznanie kluczy naturalnych jest bardzo proste: jeżeli
Poleć książkęKup książkę4.2. Mapowanie encji z tożsamością
101
atrybut klucza kandydata ma znaczenie na zewnątrz kontekstu bazy danych, to jest to
klucz naturalny bez względu na to, czy jest on generowany automatycznie. Należy
pomyśleć o użytkownikach aplikacji: jeżeli pracując z aplikacją i rozmawiając na jej
temat, użytkownicy odwołują się do atrybutu klucza, to jest to naturalny klucz (na
przykład: „Czy możesz wysłać mi zdjęcia towaru 123-abc”?).
Z doświadczenia wiemy, że naturalne klucze główne zazwyczaj sprawiają problemy.
Dobry klucz główny musi być unikatowy, niezmienny i nigdy nie może mieć wartości
null. Niewiele atrybutów encji spełnia te wymagania, a te, które to robią, nie mogą być
skutecznie indeksowane w bazach danych SQL (chociaż to jest szczegół implementacji,
który nie powinien być czynnikiem decydującym i przemawiającym za jakimś kluczem
albo przeciwko niemu). Ponadto trzeba mieć pewność, że definicja klucza kandydata
nigdy się nie zmieni przez cały czas życia bazy danych. Zmienianie wartości (albo
nawet definicji) klucza głównego i wszystkich kluczy obcych, które się do niego odwo-
łują, jest frustrującym zadaniem. Należy oczekiwać od schematu bazy danych przetrwa-
nia dziesięcioleci nawet wtedy, kiedy czas życia aplikacji będzie krótszy.
Co więcej, naturalne klucze kandydatów często można znaleźć, jedynie łącząc kilka
kolumn w złożony klucz naturalny. Takie złożone klucze, chociaż na pewno właściwe
dla niektórych artefaktów schematu (jak tabele łączące w relacji wiele-do-wielu), poten-
cjalnie znacznie utrudniają utrzymanie bazy danych, tworzenie zapytań ad hoc oraz
ewolucję schematu. Tematykę kluczy głównych omówimy w dalszej części tej książki,
w punkcie 9.2.1.
Z tych powodów zalecamy posługiwanie się sztucznymi identyfikatorami, nazy-
wanymi również kluczami zastępczymi. Klucze zastępcze nie mają żadnego bizneso-
wego znaczenia — mają unikatowe wartości wygenerowane przez bazę danych albo
aplikację. Użytkownicy aplikacji w idealnej sytuacji nie wiedzą o istnieniu tych kluczy
ani nie odwołują się do ich wartości. Są one wewnętrzną częścią systemu. Wprowadze-
nie kolumny klucza zastępczego jest także odpowiednie w często spotykanej sytuacji,
kiedy nie ma kluczy kandydatów. Inaczej mówiąc: w (prawie) każdej tabeli należącej
do schematu powinna występować dedykowana kolumna klucza zastępczego służąca
tylko temu celowi.
Istnieje kilka dobrze znanych sposobów generowania wartości kluczy zastępczych.
Konfiguruje się je za pomocą wspomnianej wyżej adnotacji @GeneratedValue.
4.2.4. Konfigurowanie generatorów kluczy
Do oznaczenia właściwość identyfikatora klasy encji potrzebna jest adnotacja @Id. Jeżeli
nie użyjemy razem z nią adnotacji @GeneratedValue, to dostawca JPA zakłada, że przed
zapisaniem egzemplarza klasy zadbamy o utworzenie i przypisanie jego identyfikatora.
Nazywamy go identyfikatorem przypisanym przez aplikację. Przypisywanie identyfi-
katora encji ręcznie jest konieczne w przypadku, gdy mamy do czynienia ze starszymi
bazami danych i (lub) naturalnymi kluczami głównymi. Więcej na temat tego rodzaju
mapowania opowiemy w punkcie 9.2.1, który jest dedykowany temu tematowi.
Zwykle chcemy, aby system wygenerował wartość klucza głównego podczas zapi-
sywania egzemplarza encji, dlatego umieszczamy adnotację @GeneratedValue obok adno-
tacji @Id. Specyfikacja JPA standaryzuje kilka strategii generowania wartości za pomocą
Poleć książkęKup książkę102
ROZDZIAŁ 4. Mapowanie klas utrwalania
wartości typu wyliczeniowego javax.persistence.GenerationType, którą wybieramy,
przypisując @GeneratedValue(strategy = ...):
GenerationType.AUTO — Hibernate wybiera właściwą strategię na podstawie
dialektu SQL skonfigurowanej bazy danych. Jest to równoważne adnotacji
@GeneratedValue() bez jakichkolwiek ustawień.
GenerationType.SEQUENCE — Hibernate oczekuje w bazie danych sekwencji
HIBERNATE_SEQUENCE (tworzy ją, jeśli posługujemy się odpowiednimi narzędziami).
Sekwencja ta będzie wywoływana osobno przed każdą instrukcją INSERT, by
wygenerować sekwencyjne wartości liczbowe.
GenerationType.IDENTITY — Hibernate oczekuje w bazie danych specjalnej, auto-
inkrementowanej kolumny klucza głównego, która w momencie wykonania
instrukcji INSERT automatycznie wygeneruje wartość liczbową (Hibernate tworzy
taką kolumnę za pomocą języka DDL).
GenerationType.TABLE — Hibernate korzysta z dodatkowej tabeli w schemacie
bazy danych, w której są zapisane następne liczbowe wartości kluczy głów-
nych — po jednym wierszu na każdą klasę encji. Wartości z tej tabeli są odczy-
tywane i odpowiednio aktualizowane przed wykonaniem instrukcji INSERT.
Domyślna nazwa tabeli to HIBERNATE_SEQUENCES, natomiast nazwy kolumn to
SEQUENCE_NAME i SEQUENCE_NEXT_HI_VALUE (w wewnętrznej implementacji zastoso-
wano złożony, ale wydajny algorytm generowania największych i najmniejszych
wartości; więcej informacji na ten temat później).
Chociaż ustawienie AUTO wydaje się wygodne, to zwykle potrzebujemy większej kon-
troli. Z tego powodu nie powinniśmy na nim polegać, a zamiast niego jawnie skonfi-
gurować strategię generowania klucza głównego. Ponadto większość aplikacji działa
z sekwencjami bazy danych, ale czasami zachodzi potrzeba spersonalizowania nazwy
i innych ustawień sekwencji. Dlatego zamiast wybierania jednej ze strategii JPA, zale-
camy zmapowanie identyfikatora za pomocą adnotacji @GeneratedValue(generator= ID_
GENERATOR ), tak jak to pokazaliśmy w poprzednim przykładzie.
Jest to niestandardowy generator identyfikatorów. Można teraz skonfigurować usta-
wienie ID_GENERATOR niezależnie od klas encji.
Specyfikacja JPA obejmuje dwie wbudowane adnotacje, z których można skorzystać
do skonfigurowania generatorów identyfikowanych przez nazwę: @javax.persistence.
SequenceGenerator oraz @javax.persistence.TableGenerator. Za pomocą tych adnotacji
można stworzyć generator z własnymi nazwami sekwencji i tabeli. Jak zwykle bywa
w przypadku adnotacji JPA, niestety można ich używać tylko na początku (być może
w innym przypadku pustej) klasy, a nie w pliku package-info.java.
Własność frameworka Hibernate
Z tego powodu, a także dlatego, że adnotacje JPA nie dają nam dostępu do pełnego
zbioru funkcji Hibernate, zalecamy rozwiązanie alternatywne: natywną adnotację
@org.hibernate.annotations.GenericGenerator. Adnotacja ta obsługuje wszystkie strategie
generatora identyfikatorów oraz szczegółów konfiguracji. W przeciwieństwie do raczej
Poleć książkęKup książkę4.2. Mapowanie encji z tożsamością
103
ograniczonych adnotacji JPA, możemy skorzystać z adnotacji Hibernate w pliku
package-info.java, zazwyczaj umieszczonego w tym samym pakiecie, w którym są umiesz-
czone klasy modelu dziedziny. Rekomendowaną konfigurację zaprezentowaliśmy na
poniższym listingu.
Listing 4.2. Generator identyfikatorów frameworka Hibernate skonfigurowany jako
metadane na poziomie pakietu
ŚCIEŻKA: /model/src/main/java/org/jpwh/model/package-info.java
strategia enhanced-sequence
@org.hibernate.annotations.GenericGenerator(
name = ID_GENERATOR ,
strategy = enhanced-sequence ,
parameters = {
@org.hibernate.annotations.Parameter(
name = sequence_name ,
value = JPWH_SEQUENCE
),
@org.hibernate.annotations.Parameter(
name = initial_value ,
value = 1000
)
})
nazwa sekwencji
wartość początkowa
Stosowanie tej specyficznej dla Hibernate konfiguracji generatora przynosi następujące
korzyści:
Strategia enhanced-sequence
generuje sekwencyjne wartości liczbowe. Jeżeli
stosowany dialekt SQL obsługuje sekwencje, to Hibernate skorzysta z sekwencji
w bazie danych. Jeśli wykorzystywany system DBMS nie wspiera natywnych
sekwencji, to framework Hibernate skorzysta z dodatkowej „tabeli sekwencji”,
która symuluje zachowanie sekwencji. W ten sposób zyskujemy rzeczywistą
przenośność: przed wykonaniem instrukcji SQL INSERT zawsze można wywołać
program generujący. Takiej swobody nie ma na przykład w przypadku stosowania
automatycznie inkrementowanych kolumn identyfikatorów, które w momencie
wykonania instrukcji INSERT generują wartość zwracaną później do aplikacji.
Możemy skonfigurować nazwę sekwencji
. Hibernate skorzysta z istniejącej
sekwencji albo utworzy ją automatycznie podczas generowania schematu SQL.
Jeżeli system DBMS nie obsługuje sekwencji, to będzie to nazwa specjalnej
„tabeli sekwencji”.
Możemy rozpocząć od wskazanej wartości początkowej
. Dostępność tego
ustawienia daje nam przestrzeń do wykorzystywania danych testowych. Przy-
kładowo przy próbie uruchomienia testów integracyjnych Hibernate będzie
wprowadzać nowe dane z kodu testów o wartościach identyfikatora większych niż
1000. Dowolne dane testowe, które chcemy zaimportować przed testem, mogą
posługiwać się wartościami od 1 do 999, a w testach możemy posługiwać się sta-
bilnymi wartościami identyfikatorów: „załaduj towar o identyfikatorze 123
i uruchom na nim jakieś testy”. Opcja jest stosowana w momencie, gdy Hibernate
generuje schemat SQL i sekwencję. Jest to opcja DDL.
Poleć książkęKup książkę104
ROZDZIAŁ 4. Mapowanie klas utrwalania
Tę samą sekwencję bazy danych możemy współdzielić pomiędzy wszystkimi klasami
modelu dziedziny. Nie ma żadnych przeszkód, aby adnotacja @GeneratedValue(generator
= ID_GENERATOR ) została umieszczona w klasach wszystkich encji. Nie ma znaczenia,
czy wartości klucza głównego są ciągłe dla jakiejś konkretnej encji, jednak pod warun-
kiem że są unikatowe w ramach jednej tabeli. W przypadkach gdy ma znaczenie rywa-
lizacja (ponieważ sekwencja musi być wywołana przed każdą instrukcją INSERT), można
zastosować odmianę omawianej konfiguracji generatora. Omówimy ją później, w pod-
rozdziale 20.1.
W roli typu właściwości identyfikatora w klasie encji używamy klasy java.lang.Long,
co doskonale pasuje do liczbowego generatora sekwencji bazy danych. Można by
również skorzystać z prymitywu long. Główna różnica polega na wartości zwracanej
przez wywołanie someItem.getId() na nowym egzemplarzu, który jeszcze nie został
zapisany do bazy danych: null albo 0. Przy sprawdzaniu, czy określony egzemplarz jest
nowy, test na wartość null jest prawdopodobnie bardziej zrozumiały dla osoby z zewnątrz,
która czyta nasz kod. Dla identyfikatorów nie należy stosować innego typu całkowito-
liczbowego, takiego jak int albo short. Chociaż przez pewien czas mogą one działać
(być może nawet przez wiele lat), to ze względu na to, że z czasem baza danych będzie
się powiększać, możemy być ograniczeni ich zakresem. Jeżeli nowy identyfikator byłby
generowany co jedną milisekundę bez żadnych przerw, to rozwiązanie bazujące na
typie int działałoby przez prawie dwa miesiące, natomiast w przypadku skorzystania
z typu long wystarczyłoby na około 300 milionów lat.
Strategia enhanced-sequence pokazana na listingu 4.2 to zalecana strategia sekwencji
dla większości aplikacji. Jest to jednak tylko jedna z wbudowanych strategii dostępnych
dla frameworka Hibernate.
Własność frameworka Hibernate
4.2.5. Strategie generatorów identyfikatorów
Poniżej zamieściliśmy listę wszystkich dostępnych strategii identyfikatorów genera-
torów, ich opcji oraz naszych zaleceń dotyczących ich użycia. Jeżeli nie chcesz teraz
czytać całej tej listy, włącz opcję GenerationType.AUTO i sprawdź domyślne zachowanie
frameworka Hibernate dla wybranego dialektu bazy danych. Istnieje duże prawdopo-
dobieństwo, że strategie sequence bądź identity będą dobre, ale nie będą to opcje
najbardziej wydajne lub przenośne. Jeżeli potrzebujesz spójnego i przenośnego zacho-
wania oraz wartości identyfikatorów dostępnych przed wykonaniem instrukcji INSERT,
skorzystaj ze strategii enhanced-sequence zaprezentowanej w poprzednim punkcie. To
przenośna, elastyczna i nowoczesna strategia, która dodatkowo oferuje różne optymali-
zatory dla dużych zbiorów danych.
Pokażemy również relację pomiędzy każdą ze standardowych strategii JPA a natywnym
odpowiednikiem Hibernate. Framework Hibernate rozwijał się organicznie. Z tego
powodu mamy teraz dwa zbiory mapowania pomiędzy standardową a natywną stra-
tegią — na liście będą one nazywane Starym i Nowym. Mapowanie można przełączyć
za pomocą ustawienia hibernate.id.new_generator_mappings w pliku persistence.xml.
Poleć książkęKup książkę4.2. Mapowanie encji z tożsamością
105
Generowanie identyfikatorów przed instrukcją INSERT albo po niej: jaka jest
różnica?
Usługa ORM próbuje zoptymalizować instrukcje SQL INSERT, na przykład łącząc je w grupy —
po kilka — na poziomie JDBC. Dzięki temu wykonanie instrukcji SQL podczas jednostki pracy
następuje jak najpóźniej, a nie w chwili wywołania entityManager.persist(someItem). Wywo-
łanie umieszcza jedynie instrukcję wstawiania w kolejce do późniejszego wykonania,
a jeśli to możliwe, przypisuje encji wartość identyfikatora. Jednak jeżeli wtedy wywołamy
someItem.getId(), to możemy uzyskać wartość null, jeśli silnik nie zdoła wygenerować
identyfikatora przed instrukcją INSERT. Ogólnie rzecz biorąc, preferujemy strategie gene-
rowania pre-insert. W wyniku zastosowania tych strategii wartości identyfikatorów są
generowane niezależnie — przed wykonaniem instrukcji INSERT. Powszechnie wybieraną
opcją jest współdzielona i dostępna równolegle sekwencja bazy danych. Kolumny autoin-
krementowane, domyślne wartości kolumn albo klucze generowane za pomocą wyzwa-
laczy są dostępne wyłącznie po wykonaniu instrukcji INSERT.
Domyślna wartość to true, co odpowiada Nowemu mapowaniu. Oprogramowanie nie
starzeje się tak dobrze, jak wino:
native — powoduje automatyczny wybór innych strategii, takich jak sequence
lub identity, w zależności od skonfigurowanego dialektu SQL. Wymaga przeglą-
dania dokumentacji Javadoc (albo nawet kodu źródłowego) dialektu SQL skonfi-
gurowanego w pliku persistence.xml. Odpowiednik JPA GenerationType.AUTO
w Starym mapowaniu.
sequence — wykorzystuje natywną sekwencję bazy danych o nazwie HIBERNATE_
SEQUENCE. Sekwencja jest wywoływana przed każdą instrukcją INSERT dla nowego
wiersza. Możemy spersonalizować nazwę sekwencji oraz wprowadzić dodatkowe
ustawienia DDL. Więcej informacji można znaleźć w dokumentacji Javadoc dla
klasy org.hibernate.id.SequenceGenerator.
sequence-identity — generuje wartości kluczy, wywołując sekwencję bazy danych
podczas wstawiania wierszy do bazy danych, na przykład insert into ITEM(ID)
values (HIBERNATE_SEQUENCE.nextval). Wartość klucza jest pobierana po wyko-
naniu instrukcji INSERT. Jest to takie samo zachowanie jak w przypadku strategii
identity. Pozwala na używanie tych samych parametrów i typów właściwości jak
w przypadku strategii sequence. Więcej informacji można znaleźć w dokumentacji
Javadoc klasy org.hibernate.id.SequenceIdentityGenerator oraz jej klasy nad-
rzędnej.
enhanced-sequence — wykorzystuje natywną sekwencję bazy danych, kiedy to
jest możliwe. W przeciwnym razie używana jest dodatkowa tabela bazy danych
z pojedynczą kolumną i wierszem, emulująca sekwencję. Domyślna nazwa to
HIBERNATE_SEQUENCE. „Sekwencja” bazy danych jest wywoływana zawsze przed
instrukcją INSERT, dzięki czemu uzyskujemy takie samo zachowanie niezależnie
od tego, czy system DBMS obsługuje rzeczywiste sekwencje. Istnieje możliwość
skorzystania z optymalizatora org.hibernate.id.enhanced.Optimizer, dzięki któremu
nie ma konieczności sięgania do bazy danych przed każdą instrukcją INSERT.
Ustawienie domyślne to brak optymalizacji i pobieranie nowej wartości dla
każdej instrukcji INSERT. Więcej przykładów można znaleźć w rozdziale 20.
Opis wszystkich parametrów można znaleźć w dokumentacji Javadoc dla klasy
Poleć książkęKup książkę106
ROZDZIAŁ 4. Mapowanie klas utrwalania
org.hibernate.id.enhanced.SequenceStyleGenerator. Odpowiednik JPA Generation
Type.SEQUENCE oraz GenerationType.AUTO z włączonym Nowym mapowaniem.
Jest to najprawdopodobniej najlepsza opcja spośród strategii wbudowanych.
seqhilo — używa natywnej sekwencji bazy danych o nazwie HIBERNATE_SEQUENCE;
optymalizuje wywołania przed instrukcjami INSERT, łącząc wartości hii lo. Jeżeli
wartość hi pobrana z sekwencji to 1, to następne dziewięć instrukcji wstawiania
do bazy będzie wykonywanych z wartościami kluczy 11, 12, 13… 19. Później
zostaje wywołana kolejna sekwencja w celu uzyskania następnej wartości hi
(2 albo większej) i procedura jest powtarzana dla wartości 21, 22, 23 itd. Istnieje
możliwość skonfigurowania maksymalnej wartości lo (9 to ustawienie domyślne)
za pomocą parametru max_lo. Niestety, ze względu na wadę kodu frame-
worka Hibernate nie można skonfigurować tej strategii za pomocą adnotacji
@GenericGenerator. Jedynym sposobem skorzystania z tej strategii jest użycie usta-
wienia JPA GenerationType.SEQUENCE i Starego mapowania. Istnieje możliwość
skonfigurowania tej strategii za pomocą standardowej adnotacji JPA @Sequence
Generator na klasie (która w innym przypadku byłaby pusta). Więcej informacji
można uzyskać w dokumentacji Javadoc dla klasy org.hibernate.id.SequenceHiLo
Generator i jej klasy nadrzędnej. Zamiast tej strategii warto zastanowić się
nad użyciem strategii enhanced-sequence z optymalizatorem.
hilo — używa dodatkowej tabeli o nazwie HIBERNATE_UNIQUE_KEY oraz tego samego
algorytmu, jak w przypadku strategii seqhilo. W tabeli występuje pojedyncza
kolumna i wiersz z następną wartością sekwencji. Domyślna maksymalna
wartość lo wynosi 32767, więc warto skonfigurować tę wartość za pomocą para-
metru max_lo. Więcej informacji można znaleźć w dokumentacji Javadoc dla klasy
org.hibernate.id.TableHiLoGenerator. Nie zalecamy stosowania tej przestarzałej
strategii. Zamiast niej lepiej wykorzystać strategię enhanced-sequence wraz
z optymalizatorem.
enhanced-table — używa dodatkowej tabeli o nazwie HIBERNATE_SEQUENCES, domyśl-
nie z jednym wierszem reprezentującym sekwencję i przechowującym następną
wartość. Ta wartość jest wybierana i aktualizowana w przypadku, kiedy musi
być wygenerowana wartość identyfikatora. Generator można skonfigurować tak,
aby zamiast tego wykorzystywał wiele wierszy: po jednym dla każdej sekwencji.
Więcej informacji można znaleźć w dokumentacji Javadoc dla klasy org.hibernate.
id.enhanced.TableGenerator. Strategia ta jest odpowiednikiem JPA Generation
Type.TABLE z włączonym Nowym mapowaniem. Zastępuje przestarzały, ale
podobny generator org.hibernate.id.MultipleHiLoPerTableGenerator, który jest
odpowiednikiem Starego mapowania dla ustawienia JPA GenerationType.TABLE.
identity — obsługuje sekwencję IDENTITY i autoinkrementowane kolumny
w bazach danych DB2, MySQL, MS SQL Server i Sybase. Wartość identyfi-
katora dla kolumny klucza głównego będzie generowana dla instrukcji SQL
INSERT wykonywanej dla wiersza. Nie pozwala na wprowadzanie opcji. Niestety,
ze względu na wadę kodu frameworka Hibernate nie można skonfigurować tej
strategii za pomocą adnotacji @GenericGenerator. Jedynym dostępnym sposobem
użycia tej strategii jest zastosowanie ustawienia JPA GenerationType.IDENTITY
Poleć książkęKup książkę4.2. Mapowanie encji z tożsamością
107
oraz Starego albo Nowego mapowania, dzięki czemu strategia stanie się domyślna
dla ustawienia GenerationType.IDENTITY.
increment — przy uruchamianiu frameworka Hibernate następuje odczytanie
maksymalnych wartości (liczbowo) kolumn klucza głównego dla tabel każdej
z encji. Za każdym razem, gdy do tych tabel są wstawiane nowe wiersze, odpo-
wiednia wartość jest zwiększana o jeden. Strategia jest szczególnie skuteczna,
gdy w aplikacji Hibernate bez klastrów jest wyłączny dostęp do bazy danych.
Nie warto jej jednak używać w żadnym innym scenariuszu.
select — Hibernate nie wygeneruje wartości klucza ani nie uwzględni kolumny
klucza głównego w instrukcji INSERT. Hibernate oczekuje od systemu DBMS
przypisania do kolumny wartości (domyślnej dla schematu albo wygenerowanej
za pomocą wyzwalacza) w momencie wstawiania wiersza do bazy danych. Następ-
nie kiedy wiersz zostanie wstawiony do bazy, Hibernate odczytuje kolumnę
klucza głównego za pomocą zapytania SELECT. Wymaganym parametrem jest key.
Za jego pomocą przekazujemy do instrukcji SELECT nazwę właściwości identy-
fikatora w bazie danych (na przykład id). Ta strategia nie jest zbyt wydajna
i powinna być stosowana tylko ze starszymi sterownikami JDBC, które nie mogą
zwrócić wygenerowanych kluczy bezpośrednio.
uuid2 — generuje w warstwie aplikacji unikatowy, 128-bitowy klucz UUID. Stra-
tegia przydatna w sytuacji, gdy potrzebujemy globalnie unikatowych identyfi-
katorów dla wielu baz danych (załóżmy, że co noc dane z kilku różnych produk-
cyjnych baz danych są scalane partiami do archiwum). Identyfikator UUID może
być kodowany w klasie encji jako java.lang.String, byte[16] albo java.util.UUID.
Strategia ta zastępuje starsze strategie uuid oraz uuid.hex. Można ją skonfigu-
rować za pomocą ustawienia org.hibernate.id.UUIDGenerationStrategy. Więcej
informacji można znaleźć w dokumentacji Javadoc dla klasy org.hibernate.
id.UUIDGenerator.
guid — wykorzystuje globalnie unikatowy identyfikator generowany przez bazę
danych za pośrednictwem funkcji SQL dostępnej dla baz danych Oracle, Ingres,
MS SQL Server i MySQL. Hibernate wywołuje funkcję bazy danych przed
wykonaniem instrukcji INSERT. Wartość jest mapowana na właściwość identyfi-
katora java.lang.String. Aby uzyskać pełną kontrolę nad generowaniem identy-
fikatora, można skonfigurować strategię za pomocą adnotacji @GenericGenerator,
podając w pełni kwalifikowaną nazwę klasy, która implementuje interfejs
org.hibernate.id.IdentityGenerator.
Podsumowując, nasze zalecenia dotyczące generatora identyfikatora są następujące:
Ogólnie rzecz biorąc, lepiej stosować strategie generowania pre-insert, polegające
na niezależnym generowaniu wartości identyfikatorów przed wykonaniem
instrukcji INSERT.
Lepiej też wykorzystywać strategię enhanced-sequence, która bazuje na użyciu
natywnej sekwencji bazy danych, jeśli jest obsługiwana, a jeśli nie, to emuluje
sekwencję za pomocą dodatkowej tabeli bazy danych z pojedynczą kolumną
i wierszem.
Poleć książkęKup książkę108
ROZDZIAŁ 4. Mapowanie klas utrwalania
Od tej chwili zakładamy, że właściwości identyfikatorów zostały dodane do klas encji
modelu dziedziny, a
Pobierz darmowy fragment (pdf)