JAVA PERSISTENCE API CZĘŚĆ 2 Waldemar Korłub ASPEKTY ZAAWANSOWANE Narzędzia i aplikacje Java EE KASK ETI Politechnika Gdańska
JPA w aplikacji Java EE Połączeniem z bazą danych zarządza serwer aplikacji Konfiguracja danych dostępowych zależna od wybranego serwera Konfiguracja nie występuje w kodzie samej aplikacji n Przenośność pomiędzy serwerami (+) n Rozproszenie konfiguracji (-) Serwer wykorzystuje pulę połączeń z bazą danych do obsługi wielu użytkowników Połączenie jest udostępniane aplikacji jako zasób JNDI (Java Naming and Directory Interface)
JPA w aplikacji Java EE Wstrzykiwanie obiektu klasy EntityManager: @PersistenceContext EntityManager em; Transakcje na serwerze Java EE Zarządzane przez kontener w warstwie EJB Zarządzane przez użytkownika (dewelopera) Serwery Java EE umożliwiają realizację transakcji rozproszonych Operacje na wielu bazach danych w jednej transakcji Jedna transakcja obejmująca wiele serwerów aplikacji n Z których każdy wykorzystuje własną bazę/bazy danych
Transakcje zarządzane przez użytkownika Wstrzyknięcie obiektu transakcji: @Resource UserTransaction utx; Operacje na bazie danych: try { utx.begin(); //... utx.commit(); } catch (Exception ex) { utx.rollback(); }
Dziedziczenie w klasach encyjnych Model domenowy może wykorzystywać hierarchię dziedziczenia Szczególnie dla złożonych domen biznesowych, np. w aplikacjach korporacyjnych Model domenowy jest utrwalany w bazie danych Konieczna jest obsługa relacji dziedziczenia w czasie mapowania obiektowo-relacyjnego
Dziedziczenie w JPA JPA obsługuje dwa scenariusze użycia: Klasa bazowa nie jest samodzielną encją n W klasie bazowej: @MappedSuperclass n W klasach pochodnych: brak dodatkowej konfiguracji n Pola klasy bazowej odzwierciedlane jako dodatkowe kolumny w tabeli klasy pochodnej Klasa bazowa jest samodzielną encją n W klasie bazowej: @Inheritance(strategy = ) @DiscriminatorColumn(name = "type") @DiscriminatorValue("book") n W klasach pochodnych: @DiscriminatorValue("comics")
Strategia mapowania (dla dziedziczenia encji) Dla dziedziczenia z wykorzystaniem encji jako klasy bazowej dostępne są 3 strategie mapowania: SINGLE_TABLE n Pojedyncza tabela dla całej hierarchii dziedziczenia TABLE_PER_CLASS n Odrębna tabela dla każdej klasy konkretnej w hierarchii dziedziczenia n Każda z tabel zawiera kolumny dla pól dziedziczonych oraz pól samej klasy JOINED n Wspólna tabela z kolumnami dla pól klasy bazowej n Dodatkowe tabele dla pól klas pochodnych Wady i zalety poszczególnych strategii
Wersjonowanie obiektów encyjnych Problem edycji tego samego obiektu encyjnego przez różnych użytkowników Systemy z wielodostępem, np. aplikacje korporacyjne Czas od pobrania encji z bazy danych do zapisu zmian może być długi n Czas spędzony przez użytkownika w formularzu edycji n Kolejny użytkownik może rozpocząć edycję encji zanim poprzedni zakończy swoją Bez wersjonowania: nadpisywanie danych Najnowszy UPDATE wygrywa
Wersjonowanie obiektów encyjnych JPA oferuje podstawowy model wersjonowania encji z optymistycznym blokowaniem (ang. optimistic locking) Optymistycznie zakładamy, że konflikt nie wystąpi n Dwóch użytkowników nie rozpocznie edycji równolegle ale jeśli jednak wystąpi, zablokujemy możliwość nadpisania danych o wyższym numerze wersji Przydatne gdy konflikty występują rzadko n Większość zapisów kończy się powodzeniem n Sporadyczne odrzucenia nie są uciążliwe dla użytkowników n Aplikacja powinna pomóc użytkownikowi w scaleniu danych z konfliktujących wersji
Dla porównania: blokowanie pesymistyczne Pesymistycznie zakładamy, że nastąpi konflikt Aby mu zapobiec blokujemy możliwość rozpoczęcia edycji, jeśli inny użytkownik ją rozpoczął Informujemy użytkownika o zaistniałej sytuacji np. Nie możesz edytować tego dokumentu, bo jest on edytowany przez użytkownika Waldemar Korłub od 2016-10-18 10:45 GMT
Wersjonowanie w JPA Wersjonowana klasa encyjna powinna zawierać pole wersji: @Version private int version; JPA zwiększa numer wersji w bazie danych przy każdym zapisie Jeśli wersja w bazie danych jest większa niż wersja zapisywanego obiektu encyjnego odrzucenie zapisu
JPA a SQL JPA jest warstwą abstrakcji ponad językiem SQL Zapytania SQL są generowane przez bibliotekę ale deweloper ma wiele możliwości, aby na nie wpływać i specyfikować pożądane zachowania Domyślne zachowania są właściwe dla najczęstszych przypadków użycia W innych przypadkach mogą skutkować poważnymi problemami z wydajnością aplikacji Wykorzystanie JPA nie zwalnia dewelopera ze znajomości bazy danych i mechanizmów jej działania!
Logowanie zapytań SQL Możliwość logowania zapytań SQL ułatwia diagnozowanie problemów Konfiguracja w pliku persistence.xml (po stronie aplikacji): Hibernate: <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> EclipseLink: <property name="eclipselink.logging.level.sql" value="fine"/> <property name="eclipselink.logging.parameters" value="true"/>
Logowanie zapytań SQL Zaleca się konfigurację poziomów logowania na serwerze zamiast użycia pliku persistence.xml Hibernate na serwerze Wildfly: Plik standalone/configuration/standalone.xml Poziomy logowania definiowane w sekcji: <subsystem xmlns="urn:jboss:domain:logging:3.0"> Logowanie zapytań SQL: <logger category="org.hibernate.sql"> <level name="debug"/> </logger>
Logowanie zapytań SQL Hibernate na serwerze Wildfly ciąg dalszy: Logowanie wartości parametrów zapytań SQL: <logger category="org.hibernate.type.descriptor.sql"> <level name="trace"/> </logger> Logowanie informacji o grupowaniu zapytań: <logger category="org.hibernate.engine.jdbc.batch"> <level name="debug"/> </logger>
Logowanie zapytań SQL Hibernate na serwerze Wildfly ciąg dalszy: Aby informacje były widoczne na konsoli konieczne jest ustawienie poziomu wyświetlanych komunikatów n TRACE lub DEBUG w zależności od wcześniejszych ustawień: <console-handler name="console"> <level name="trace"/> <formatter> <named-formatter name="color-pattern"/> </formatter> </console-handler>
Grupowanie zapytań (ang. batch queries) Popularne implementacje JPA przy ustawieniach domyślnych nie grupują zapytań SQL Każdy INSERT/UPDATE wykonywany jako osobne zapytanie Narzut po stronie bazy danych na parsowanie zapytań W przypadku wielu zapisów w jednej transakcji grupowanie znacząco skraca czas obsługi Konfigurację grupowania może określić w pliku persistence.xml
Grupowanie zapytań INSERT/UPDATE (biblioteka Hibernate) Maksymalna liczba grupowanych zapytań: <property name="hibernate.jdbc.batch_size" value="50"/> Pojedynczy batch może odnosić się do jednej tabeli Problem grupowania przy zapisywaniu na zmianę obiektów różnych typów Szeregowanie operacji: <property name="hibernate.order_inserts" value="true"/> <property name="hibernate.order_updates" value="true"/> Obsługa grupowania dla wersjonowanych encji: <property name="hibernate.jdbc.batch_versioned_data" value="true"/> Hibernate wyłączy grupowanie jeśli napotka: @GeneratedValue(strategy = GenerationType.IDENTITY)
Grupowanie w zapytaniach SELECT (biblioteka Hibernate) Liczba obiektów wczytywanych jednorazowo w czasie ładowania powiązanych encji: <property name="hibernate.default_batch_fetch_size" value="20"/> Liczba wierszy odczytywanych jednorazowo z kursora bazodanowego dla zapytania SELECT: <property name="hibernate.jdbc.fetch_size" value="50"/>
Studium przypadku: Problem 1+N zapytań Przykładowa aplikacja (JPA_1_plus_N) Na rozgrzewkę: grupowanie zapytań INSERT Problem 1+N zapytań przy ładowaniu danych @ManyToMany(fetch = ): n FetchType.LAZY pobieranie danych z bazy na żądanie n FetchType.EAGER pobranie wszystkich danych bezpośrednio po załadowaniu encji (niezalecane) n Wpływ ustawienia hibernate.default_batch_fetch_size na sposób pobierania danych Składnia LEFT JOIN FETCH
Grafy encji (ang. entity graphs) Sposób ładowania powiązanych obiektów encyjnych można również określić za pomocą grafu encji Domyślny graf encji wynika z wartości atrybutów fetch w adnotacjach @OneToOne, @OneToMany itd. n Nie zaleca się używania strategii FetchType.EAGER n Jeśli użyto wyłącznie strategii FetchType.LAZY, to domyślny graf encji nie zawiera żadnych związków Pożądany graf encji należy podać jako wskazówkę (ang. hint) dla JPA w czasie budowania zapytania
Grafy encji (ang. entity graphs) Związki występujące w grafie są ładowane w ramach jednego zapytania Pozostałe związki są ładowane w zależności od użytej wskazówki: javax.persistence.loadgraph pozostałe związki zgodnie z grafem domyślnym javax.persistence.fetchgraph pozostałe związki nie są ładowane UWAGA: biblioteka Hibernate traktuje opcję fetchgraph tak samo jak loadgraph https://hibernate.atlassian.net/browse/hhh-8776
Definicja grafu W klasie encyjnej: @NamedEntityGraphs({ @NamedEntityGraph( name = Invoice.Graphs.WITH_ADDRESS, attributenodes = {@NamedAttributeNode("address")}) }) public class Invoice implements Serializable { public static class Graphs { public static final String WITH_ADDRESS="Invoice{address}"; } } //... @ManyToOne Address address;
Wykorzystanie grafu Dla obiektu klasy Query: TypedQuery<Invoice> query = em.createquery( "SELECT a FROM Invoice a", Invoice.class); query.sethint("javax.persistence.loadgraph", em.getentitygraph(invoice.graphs.with_address)); List<Invoice> invoices = query.getresultlist(); W metodzie EntityManager.find(): EntityGraph entitygraph = em.getentitygraph(invoice.graphs.with_address); Invoice invoice = em.find(invoice.class, id, singletonmap("javax.persistence.loadgraph", entitygraph));
Pytania?