Technologie dostępu do baz danych z aplikacji Javy: ● JDBC – (ang. Java DataBase Connectivity - łącze do baz danych w języku Java) - interfejs programowania opracowany w 1996 r. przez Sun Microsystems, umożliwiający niezależnym od platformy aplikacjom napisanym w języku Java porozumiewać się z bazami danych za pomocą języka SQL.Jest to podstawowy interfejs dostępu do baz danych – lecz ręczne kodowanie w JDBC jest uciążliwe i podatne na błędy ● SQLJ – jest metodą osadzania instrukcji SQL w kodzie JAVA, które następnie przy użyciu translatora SQLJ zamieniane są na kod JAVA– Jest to odporna na błędy alternatywa dla bezpośredniego korzystania z JDBC ● Encyjne EJB (Enterprise JavaBeans)– reprezentują w sposób obiektowy dane (np. przykrywają relacyjną bazę danych).Wszelki dostęp do danych (w tym i masowy) odbywa się poprzez obiekty napisane w języku Java, a zatem niesie ze sobą ograniczenia wydajnościowe i zajętości zasobów.Ziarenka encyjne mimo ich konkretnych zadań określonych w specyfikacji EJB, w praktyce nie są przenośne.Nie poddają się również serializacji,przez co trzeba definiować dodatkowe obiekty transferu danych (DTO – Data Transfer Object).EJB wymusza nienaturalny styl programowania w Javie i utrudnia ponowne wykorzystanie kodu poza konkretnym kontenerem. Obecnie trwają prace nad przebudowaną wersją 3.0 specyfikacji ● Biblioteka znaczników JSTL SQL – Wygodna w prostych aplikacjach opartych na JSP – Miesza logikę biznesową z logiką prezentacji, narusza model MVC MVC (ang. Model-View-Controller) - Model-Widok-Kontroler to wzorzec projektowy w informatyce, którego głównym założeniem jest wyodrębnienie trzech podstawowych komponentów aplikacji: • modelu danych • interfejsu użytkownika • logiki sterowania w taki sposób, aby modyfikacje jednego komponentu minimalnie wpływały na pozostałe. ● Technologie odwzorowania obiektowo-relacyjnego (ang. O/RM (ORM) – object/relational mapping)– Efektywnie mapują świat obiektów na świat relacyjnej bazy danych.Do podstawowych zalet O/RM należą: – Produktywność (uniknięcie tworzenia uciążliwego kodu obsługującego bazę danych) – Łatwość pielęgnacji aplikacji (mniej kodu, elastyczność) – Wydajność (przy założeniu ograniczonego czasu i budżetu na zbudowanie i optymalizację aplikacji) – Niezależność od konkretnego SZBD Technologie te najczęściej działają w warstwie webowej aplikacji. W chwili obecnej stanowią najlepsze rozwiązanie. Rozwiązanie ORM składa się z 4 podstawowych elementów: ● interfejsu do przeprowadzania podstawowych operacji CRUD na obiektach klas zapewniających trwałość ● języka lub interfejsu programistycznegodo określania zapytań związanych z klasami lub ich właściwościami ● narzędzia do określania metadanych ● technik interakcji z obiektami trwałymi m.in. – Wykrywanie zmian w trwałych obiektach – Różne sposoby pobierania obiektów powiązanych asocjacjami – Techniki optymalizacji Popularne implementacje technologii odwzorowania obiektowo-relacyjnego dla aplikacji Java: ● Hibernate (Open Source) ● Oracle Toplink (rozwiązanie firmowe Oracle) ● JDO (Java Data Objects - specyfikacja firmy Sun) ● JPA (Java Persistence API) O Hibernate Hibernate - Jeden z najlepiej ocenianych frameworków do realizacji warstwy dostępu do danych (ang. persistance layer). Zapewnia on przede wszystkim translację (ang. O/R mapping) danych pomiędzy relacyjną bazą danych a światem obiektowym. Opiera się na wykorzystaniu opisu struktury danych za pomocą języka XML, dzięki czemu można "rzutować" obiekty, stosowane w obiektowych językach programowania, takich jak Java bezpośrednio na istniejące tabele bazy danych. Dodatkowo Hibernate zwiększa wydajność operacji na bazie danych dzięki buforowaniu i minimalizacji liczby przesyłanych zapytań. Jest to projekt rozwijany jako open source. Głównym inicjatorem i liderem projektu jest Gavin King. Według dokumentacji celem Hibernate jest zwolnienie projektanta aplikacji z konieczności ręcznego kodowania 95% zadań związanych z zapewnieniem trwałości danych. Hibernate jest najbardziej odpowiedni dla aplikacji Java pracujących w warstwie pośredniej Hibernate jest tworzony przez firmę Jboss. Możemy go pobrać ze strony http://www.hibernate.org. Dystrybucja zawiera bibliotekę Hibernate i wykorzystywane przez nią biblioteki od innych dostawców (znajdujące się w katalogu /lib).Hibernate został podzielony na kilka części: • • • Hibernate Core - podstawowa część biblioteki, wykonująca większość pracy, Hibernate Annotations - element pozwalający zastąpić większą część konfiguracji Hibernate, budowanej do tej pory w XML, poprzez adnotacje w kodzie napisane z użyciem Javy 5. Hibernate Tools – które zawierają rozmaite wtyczki (plug-ins) dla Anta oraz darmowego środowiska Eclipse IDE.Wtyczka Hibernate Tools dla Eclipse oferuje: – Edytor odwzorowań (uzupełnianie i kolorowanie kodu) – Konsolę Hibernate (przegląd konfiguracji, klas, itp. oraz interaktywne zapytania HQL – Kreatory i generatory kodu (w tym kompletny reverse-engineering istniejącego schematu bazy danych) Zalety Hibernate: ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● ● Najpopularniejsza obecnie implementacja odwzorowania obiektowo-relacyjnego dla Javy(13 000 pobrań/miesiąc) – Dużo materiałów i pomocy w Internecie – Udział twórców Hibernate w pracach nad specyfikacją EJB 3.0 Open source (Rozpowszechniany na licencji FSF Lesser GNU Public License) Wsparcie dla stylu programowania odpowiedniego dla Javy (Hibernate: Relational Persistence For Idiomatic Java) – Obsługa asocjacji, kompozycji, dziedziczenia, polimorfizmu, kolekcji Wysoka wydajność i skalowalność – Dual-layer cache, możliwość wykorzystania w klastrze – UPDATE tylko dla zmodyfikowanych obiektów i kolumn Własne API Możliwość stworzenia dużej bazy bez większego wkładu pracy Pozwala uniknąć ręcznego zarządzania danymi na poziomie SQL i JDBC Będzie wykorzystane w JBoss CMP 2.0 engine Nie ma potrzeby implementowania interfejsów Korzystamy z możliwości SQL bez wychodzenia poza granicę języka Java Wiele sposobów wydawania zapytań: – HQL – własne przenaszalne rozszerzenie SQL – Natywny SQL – Zapytania przez obiekty Javy: Criteria i Example Dowolna klasa może być klasą reprezentującą encję Wygodne i intuicyjne mapowanie plików struktury XML na klasy Mniej kodu = mniej błędów Optymalna wydajność Trwałość dla JavaBeans Wsparcie dla bardzo rozwiniętych modeli obiektowych Rozbudowane zapytania Jeden krok dalej od zwykłego wykorzystania JDBC Ogólna architektura Hibernate: Hibernate stanowi warstwę abstrakcji pośredniczącą w komunikacji aplikacji z bazą danych, obsługując trwłaość obiektów aplikacji.Opcje konfiguracyjne Hibernate, takie jak np. parametry połączenia z bazą danych zawiera plik konfiguracyjny Hibernate, który może mieć postać pliku .properties (o nazwie hibernate.properties) lub, co jest wygodniejsze, postać pliku XML (o nazwie hibernate.cfg.xml).Obsługując trwałość obiektów,Hibernate wykorzystuje informacje o odwzorowaniu obiektowo-relacyjnym modelu danych aplikacji zawarte w plikach XML. Szczegółowa architektura Hibernate: Interfejsy Hibernate w poszczególnych warstwach: Interfejsy Hibernate przedstawione na rysunku można podzielić następująco: - Interfejsy wywoływane przez aplikacje w celu wykonania prostych operacji CRUD i wyszukiwania. Interfejsy te stanowią główny punkt styku logiki biznesowej z Hibernate. Zawierają interfejsy: Session, Transaction i Query. Interfejsy wywoływane przez kod infrastruktury aplikacji w celu konfiguracji Hibernate. Jest to przede wszystkim klasa Configuration. Interfejsy wywołań zwrotnych pozwalające aplikacji reagować na zdarzenia zachodzące wewnątrz Hibernate. Są to interfejsy: Interceptor, Lifecycle i Validatable. Interfejsy zapewniające rozszerzanie rozbudowanej funkcjonalności odwzorowania w Hibernate. Są to interfejsy: UserType, CompositeUserType i IdentifierGenerator. Interfejsy te implementuje kod infrastruktury aplikacji (jeśli to konieczne). Hibernate wykorzystuje istniejące interfejsy programistyczne Javy, włączając w to JDBC, JTA (Java Transaction API) oraz JNDI (Java Naming and Directory Interface). JDBC zapewnia odpowiedni poziom abstrakcji funkcji wspólnych dla różnych systemów relacyjnych baz danych, co oznacza, że Hibernate potrafi skorzystać z dowolnej bazy danych ze sterownikiem JDBC. Interfejsy podstawowe Pięć interfejsów podstawowych pojawia się w niemal każdej aplikacji wykorzystującej Hibernate. Dzięki nim możliwe staje się pobieranie i zapamiętywanie trwałych obiektów oraz sterowanie transakcjami. Interfejs Session Interfejs Session to główny interfejs każdej aplikacji Hibernate. Egzemplarze Session są lekkie — koszt ich utworzenia i zniszczenia bywa niewielki. Jest to niezwykle istotne, bo każda aplikacja cały czas tworzy i usuwa coraz to nowe sesje, prawdopodobnie przy każdym nowym żądaniu. ObiektSession reprezentuje jednostkę pracy („unit of work”),jest tworzony dla pojedynczego procesu.Znaczenie sesji Hibernate można opisać jako coś pomiędzy połączeniem i transakcją. Najprościej traktować sesję jako bufor lub kolekcję załadowanych obiektów powiązanych z jedną jednostką zadaniową. Hibernate potrafi wykryć zmiany w obiektach tej jednostki zadaniowej. Często interfejs Session nazywa się zarządcą trwałości, gdyż zapewnia dostęp do podstawowych operacji trwałości takich jak zapis i pobieranie obiektów. Warto podkreślić, że sesja Hibernate nie ma nic wspólnego z sesją warstwy połączenia internetowego, na przykład HttpSession. Interfejs SessionFactory Interfejs SessionFactory został zaprojektowany z myślą o współdzieleniu przez wiele wątków aplikacji. Najczęściej na całą aplikację występuje tylko jeden obiekt SessionFactory tworzony w momencie jej inicjalizacji. Jeśli jednak aplikacja korzysta z kilku baz danych w połączeniu z Hibernate, potrzeba osobnego obiektu SessionFactory dla każdej bazy danych. Obiekt SessionFactorysuży dotworzenia obiektówSession, poprzez które następnie realizowana jest komunikacja z bazą danych. Obiekt SessionFactory buforuje wygenerowane polecenia SQL i inne meta-dane odwzorowania stosowane przez Hibernate w trakcie działania aplikacji. Dodatkowo buforuje dane, które zostały odczytane w jednej jednostce zadaniowej i mogłyby przydać się w innej jednostce zadaniowej. Interfejs Configuration Obiekt Configuration służy do konfiguracji i uruchomienia Hibernate. Aplikacja używa egzemplarza Configuration do wskazania położenia dokumentów odwzorowań i właściwości specyficznych dla Hibernate. Interfejs Transaction Interfejs Transaction jest opcjonalny. Aplikacje Hibernate nie muszą z niego korzystać, jeśli chcą zarządzać transakcjami we własnym zakresie. Interfejs Transaction oddziela aplikację od implementacji transakcji ,tzn. stara się ukryć szczegóły implementacji konkretnych mechanizmów transakcyjnych: JDBC, klasy UserTransaction z JTA.W ten sposób zapewnia aplikacji jednorodny sposób ich obsługi. Ułatwia to zachowanie przenośności aplikacji Hibernate między różnymi środowiskami wykonywania i kontenerami. Często w sesji występuje tylko jedna transakcja, ale możliwe wiele kolejnych transakcji w jednej sesji. Hibernate nie pozwala na tryb auto-commit. Interfejsy Query i Criteria Interfejs Query umożliwia wysyłanie zapytań do bazy danych i sterowanie procesem ich wykonywania. Zapytania pisze się w języku HQL lub też w odpowiednim dla danej bazy danych dialekcie SQL. Egzemplarz Query odpowiada za dowiązanie parametrów zapytania, ograniczenie liczby zwracanych wyników i wykonanie zapytania. Interfejs Criteria jest bardzo podobny. Umożliwia utworzenie i wykonanie obiektowych kryteriów wyszukiwania. Egzemplarz Query nie daje się zastosować poza egzemplarzem Session, dla którego powstał. Interfejsy wywołań zwrotnych Interfejsy wywołań zwrotnych umożliwiają aplikacji otrzymywanie powiadomień, gdy coś ciekawego stanie się ze sprawdzanym obiektem — na przykład zostanie załadowany, zapisany lub usunięty. Aplikacje stosujące Hibernate nie muszą korzystać z wywołań zwrotnych, ale niejednokrotnie przydają się one do tworzenia ogólnej funkcjonalności, na przykład automatycznego tworzenia zapisów audytorskich. Interfejsy Lifecycle i Validatable zapewniają trwałym obiektom reakcję na zdarzenia związane z własnym cyklem życia trwałego. Cykl życia ma nierozerwalny związek z operacjami CRUD obiektu. Interfejs Interceptor wprowadzono, by aplikacje mogły przetwarzać wywołania zwrotne bez potrzeby wprowadzania implementacji interfejsów Hibernate do klas trwałości. Implementacje interfejsu Interceptor wprowadza się do egzemplarzy trwałych klas jako parametry. Warstwa prezentacji Klientem może być samodzielna aplikacja napisana w Javie, instalowana na komputerze (thick client), aplikacja zbudowana w oparciu o strony WWW z, której korzystamy poprzez przeglądarkę internetową z dowolnego miejsca (thin client), lub aplikacja dla urządzeń mobilnych napisana w Javie (Java 2 Micro Edition). Warstwa biznesowa Warstwa zawierająca całą logikę biznesową aplikacji. Umożliwia dostęp do źródeł danych, integracje nowego systemu w technologii J2EE z istniejącymi systemami informatycznymi w przedsiębiorstwie. Warstwa osłania system informatyczny przed warstwą prezentacji widoczną przez klienta. Warstwa biznesowa może być niezależna od technologii w jakiej została zrealizowana warstwa danych. Warstwa danych Warstwa danych zapewnia trwałość danych dla aplikacji. Trwałość może być realizowana przez relacyjną bazę danych, system katalogowy, system plików, czy obiektową bazę danych. Architektura wielowarstwowa znaczne upraszcza proces projektowania, tworzenia oraz wdrażania rozwiązań biznesowych. Kod aplikacji używa interfejsów programistycznych Session i Query z Hibernate do wykonywania operacji trwałości danych.Zajmuje się również zarządzaniem transakcjami, używając interfejsu Transaction z Hibernate. Obiekt zapytania jest tworzony metodą createQuery() obiektu Session. Zatwierdzenie transakcji następuje po wywołniu metody commit() obiektu Transaction (do wycofania transakcji służy metoda rollback()) Wyniki zapytania zostają zwrócone jako kolekcja typu List metodą list()obiektu Query. Pula połączeń określa liczbę połączeń, jakie mogą zostać nawiązane, dodatkowo odpowiada za zarządzanie połączeniami. Hibernate potrafi zintegrować się z dowolną pulą połączeń, sam tworzy pule połączeń po podaniu wymaganych parametrów. Plik konfiguracyjny informuje, w jaki sposób Hibernate może uzyskać (lub utworzyć nowe) połączenia JDBC. Ogólnie nie zaleca się tworzenia połączeń za każdym razem, gdy tylko chce się skorzystać z bazy danych, ponieważ uzyskiwanie nowych połączeń oraz utrzymywanie wielu niewykorzystywanych połączeń jest kosztowne. Stany obiektów w Hibernate W Hibernate mamy następujące stany obiektów: ● Transient (ulotny) W Hibernate obiekty utworzone operatorem new nie stają się automatycznie trwałe. Ich stan jest ulotny, czyli nie są związane z żadnym wierszem bazy danych. Znikną, gdy tylko przestanie istnieć referencja do nich w całej aplikacji. Cykl życia obiektów tego typu jest ściśle związany z ich dostępnością. Kończy się, gdy przestają być dostępne i mogą zostać usunięte przez mechanizm odzyskiwania pamięci. Hibernate traktuje wszystkie egzemplarze ulotne jako nietransakcyjne — tzn, że modyfikacja stanu takiego obiektu nie wykonuje się w kontekście żadnej transakcji. ● Persistent (trwały) Obiekt trwały to dowolny egzemplarz o tożsamości bazodanowej. Obiekt trwały mógł zostać utworzony przez aplikację, a następnie uczyniony trwałym przez wywołanie metody save() zarządcy trwałości (obiektu Session).Po tej operacji obiekt jest związany z sesją. Ewentualnie egzemplarz obiektu trwałego może powstać na podstawie informacji z bazy danych uzyskanych w wyniku wykonania zapytania, wyszukania identyfikatora lub przejścia do niego przez graf obiektów z innego obiektu. Innymi słowy, obiekty trwałe zawsze mają związek z obiektem Session i są transakcyjne. Hibernate wykrywa zmiany dokonane na trwałych obiektach i synchronizuje ich stan z bazą danych na końcu transakcji. Oprogramowanie ORM musi wykrywać, które z obiektów trwałych zmieniły się w czasie trwania transakcji. Sprawdzenie to nosi nazwę automatycznego sprawdzania zabrudzenia (obiekt z modyfikacjami, które nie zostały jeszcze zapisane w bazie danych, traktuje się jako brudny). W momencie zatwierdzania transakcji stan obiektu znajdujący się w pamięci trafia do bazy danych w wyniku wykonania polecenia INSERT, UPDATE lub DELETE. Obiekt trwały może stać się ulotnym po wywołaniu metody delete() interfejsu zarządcy trwałości powodującej usunięcie z bazy danych wiersza z danymi obiektu. ● Detached (odłączony) W skrócie można powiedzieć, że jest to obiekt, który był trwały, ale jego sesja zakończyła się. W Hibernate w momencie wywołania metody close() obiektu Session egzemplarze tracą połączenie z zarządcą trwałości. Obiekty te uważa się za odłączone, gdyż nie gwarantuje się synchronizacji ich danych z informacjami w bazie danych, bo nie są od tego momentu zarządzane przez Hibernate. Hibernate dopuszcza ponowne użycie obiektu w nowej transakcji przez jego powiązanie z nowym zarządcą (po przypisaniu obiekt ponownie traktuje się jako trwały). Ta cecha w znaczącym stopniu wpływa na sposób projektowania aplikacji wielowarstwowych. Możliwość zwrócenia obiektów z jednej transakcji do warstwy prezentacji i ponowne użycie ich w innej transakcji stanowi jeden z głównych powodów popularności Hibernate. Hibemate jawnie udostępnia również operację odłączenia — metodę evict() obiektu Session. Stosuje się ją jednak głównie w zarządzaniu buforami (ze względów wydajnościowych). Dokonanie jawnego odłączenia nie jest operacją typową. Wszystkie obiekty pobrane w trakcie trwania transakcji są niejawnie odłączane w momencie zamykania sesji lub w momencie ich serializacji (na przykład w celu przesłania do innego komputera). Przykłady Zapisu i odczytu obiektu do/z bazy danych: SessionFactory sf = new Configuration().configure().buildSessionFactory(); Session s = sf.openSession(); // rozpoczęcie sesji Transaction tx = s.beginTransaction(); // rozpoczęcie transakcji • Uczynienie nowego obiektu trwałym: Osoba o= new Osoba(); o.setImie("Zbigniew"); o.setNazwisko("Boniek"); s.save(o); tx.commit(); s.close(); // zapis obiektu do bazy danych // zatwierdzenie transakcji (wycofanie: tx.rollback()) // zakończenie sesji • Odczyt obiektu o znanym identyfikatorze: Osoba o = (Osoba) s.load(Osoba.class, new Long(20)); System.out.println(o.getImie()); Osoba o = (Osoba) s.get(Osoba.class, new Long(20)); System.out.println(o.getImie()); //Wyjątek gdy błędny klucz // Zwraca null gdy błędny klucz • Usuwanie obiektu: s.delete(o); // obiekt stanie się ulotny,ale referencja może zostać • Modyfikacja trwałego obiektu (w czasie otwartej sesji Session s): o.setNazwisko("Smolarek"); s.flush(); // jeśli zmiany mają natychmiast powędrować do bazy Elementy Hibernate: Trwałe klasy (Persistent classes): ● Trwałe klasy to klasy w aplikacji, które implementują encje występujące w reprezentowanej rzeczywistości: – Np. Osoba,Użytkownik,Klient, Faktura – Nie wszystkie instancje trwałej klasy muszą być trwałe public class Osoba { private Long id; private String imie; private String nazwisko; Możliwa odpowiadająca tabela w bazie danych: public Osoba() {} public void setId(Long id) { this.id = id; } public Long getld() { return id; } public void setImie(String imie) { this.imie = imie; } public String getImie() { return imie; } public void setNazwisko(String nazwisko) { this. nazwisko = nazwisko; } public String getNazwisko () { return nazwisko; } } Hibernate najlepiej działa z klasami spełniającymi reguły POJO (Plain Old Java Object) Reguły POJO dla Hibernate są następujące: – Metody set/get (accessor/mutator)(Metody dostępowe) dla trwałych atrybutów klasypól w tabeli, dzięki którym mamy dostęp do prywatnych pól klasy – Bezargumentowy konstruktor (może być domyślny)-jest wymagany przez Hibernate do tego, aby klasa mogła być utrwalana w bazie. – Identyfikator (opcjonalnie) – Oprócz domyślnego konstruktora dla każdej klasy, możemy dostarczyć także konstruktor umożliwiający bezpośrednie przypisanie pól innych niż klucze podstawowe. Dzięki temu możemy stworzyć i wypełnić obiekt w jednej czynności zamiast kilku. – Klasa nie może być zadeklarowana jako final – Może być final, ale w pewnych przypadkach może to np. ograniczać możliwości strojenia wydajności – Hibernate może wewnętrznie zarządzać identyfikatorami obiektu (ale jest to niezalecane) Odwzorowanie obiektowo-relacyjne, czyli pliki mapujące (*.hbm.xml): ● ● Odwzorowanie definiowane jest w pliku XML.W zasadzie nazwa pliku i jego umiejscowienie w strukturze plików nie jest istotne, jednakże przyjęło się, że każda klasa, która będzie obsługiwana przez Hibernate, powinna posiadać swój własny plik mapowania, który znajduje się w katalogu odpowiadającym pakietowi mapowanej klasy z nazwą odpowiadającą nazwie klasy z rozszerzeniem hbm.xml. Język do opisu odwzorowania jest zorientowany na Javę, tzn że odwzorowanie budujemy z punktu widzenia klasy Java a nie tabeli. Dokumenty opisujące odwzorowanie można tworzyć ręcznie lub korzystać z dostępnych narzędzi: - Generujących odwzorowanie z POJO (np. Xdoclet) - Generujących POJO i odwzorowanie z tabel bazy danych na zasadzie reverse engineering (np. Hibernate Tools) Dokumenty opisujące odwzorowanie są wskazywane w pliku konfiguracyjnym hibernate.cfg.xml (lub też programowo w aplikacji): <?xml version='1.0'encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> ••• <mapping resource="hib/Osoba.hbm.xml"/> // Ścieżka do pliku z odwzorowaniem </session-factory> </hibernate-configuration> ● ● ● Odwzorowane klasy muszą deklarować klucz główny tabeli co jest realizowane poprzez element <id>.Istnieje możliwość stosowania złożonych kluczy głównych z Hibernate, odwzorowanych przez <composite-id>, ale nie jest to zalecane.Nazwa pola jest określona poprzez atrybut name a nazwa kolumny w tabeli poprzez atrybut column. Zalecane jest stosowanie sztucznego identyfikatora, typu nie-prostego (możliwość przypisania null).Klucz zastępczy jest to dowolna wartość (zazwyczaj liczbowa) o typie danych zależnym od liczby spodziewanych obiektów (np. 32-bitowy, 64-bitowy, etc.). Poza bazą danych klucz zastępczy nic nie znaczy – nie jest to ani numer klienta, ani numer telefonu, ani nic innego. Z tego względu, jeśli jakaś decyzja biznesowa duplikuje dotychczas niepowtarzalne dane, nie spowoduje to problemów, ponieważ biznesowe nie tworzą klucza podstawowego. W definicji identyfikatora występuje element opisujący sposób generacji jego wartości <generator>.Może on przyjmować następujące wartości: – assigned: aplikacja musi wypełnić wartość (strategia domyślna) – increment: generacja programowa (działa gdy jeden proces w danej chwili może wstawiać wiersz do tabeli) – hilo: Generuje niepowtarzalne wartości typu long wykorzystując algorytm HIGH-LOW. – native: wartości klucza mają być generowane domyślnym natywnym sposobem dla wykorzystywanej bazy danych.(identity, sequence , hilo) – identity: wsparcie dla automatycznie generowanych wartości kolumny w DB2, MySQL, MS SQL Server, Sybase and HypersonicSQL (np. AUTO_INCREMENT w MySQL) – sequence: generuje niepowtarzalną wartość wykorzystując konstrukcję SEQUENCE dostępną w bazach danych DB2, Interbase, Oracle, PostgreSQL i SAP DB. – select: pobiera wartość ustawioną przez wyzwalacz (trigger) w bazie danych. Możliwe typy danych w Hibernate: – integer, long, short, float, double, character, byte, boolean (BIT), yes_no (CHAR(1): Y/N), true_false (CHAR(1): T/F): odwzorowanie typów prostych Javy na ● odpowiednie typy SQL (vendor-specific) – string: odwzorowanie java.lang.String na VARCHAR (w Oracle VARCHAR2) – date, time, timestamp: odwzorowanie java.util.Date na DATE, TIME, TIMESTAMP – big_decimal, big_integer: odwzorowanie z java.math.BigDecimal i java.math.BigInteger na NUMERIC (w Oracle NUMBER) – calendar, calendar_date, locale, timezone, currency, class, binary, text, serializable, clob, blob. Element property może przyjmować następujące wartości: name="nazwa_atrybutu" column="nazwa_kolumny" // Nazwa kolumny w bazie danych - domyślnie = name type="typ_danych" update="true|false" // Domyślnie true (false dla pól wyliczeniowych) insert="true|false" formula="arbitrarySQLexpression" access="field|property|ClassName" lazy="true|false" // włącza tryb “lazy fetching” unique="true|false" not-null="true|false" // Domyślnie false optimistic-lock="true|false" generated="never|insert|always" node="element-name|@attribute-name|element/@attribute|." index="nazwa_indeksu" unique_key="unique_key_id" length="L" precision="P" scale="S" Przykładowy plik - Osoba.hbm.xml <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="hib"> <class name="Osoba" table="Osoba"> // Odwzorowanie klasy na tabelę <id name="id" type="long" column="NumOsob"> // Definicja identyfikatora <generator class="sequence"/> <param name="sequence">osob_seq</param> </generator> </id> <property name="imie" column="Imie" type="string" // Nazwa kolumny w bazie danych - domyślnie = name // Typ danych Hibernate // Domyślnie false // Domyślnie true (false dla pól wyliczeniowych) not-null="true" update="true" insert="true"/> <property name=”nazwisko” column=”Nazwisko” type=”string”/> </class> </hibernate-mapping> Konfigurację można zapisać w pliku XML bądź, za pomocą adnotacji w kodzie Java. Ten drugi sposób jest zdecydowanie wygodniejszy, szczególnie gdy nie przewidujemy częstych zmian w schemacie tabel: @Entity @Table(name = "Osoba") public class Osoba { @Id @GeneratedValue @Column(name = "Num_osob") private Long id; @Column(name = "Imie") private String imie; @Column(name = "Nazwisko") private String nazwisko; } Adnotacja @Entity określa klasę encji.Encja(ang.entity) to lekki obiekt służący do reprezentacji trwałych danych. Typowo encja reprezentuje tabelę z relacyjnej bazy danych, ale istnieje również możliwość odwzorowania encji na kilka tabel. Encja definiowana jest w formie klasy encji. Niekiedy klasa encji jest uzupełniana o klasy pomocnicze np. klasę definiującą strukturę złożonego klucza głównego. @Id oznacza, że pole jest kluczem głównym, @GeneratedValue informuje że poszczególne wartości pola mają być automatycznie generowane, a @Column definiuje nazwę kolumny w tabeli (Osoba), z którą dane pole ma być powiązane. Rodzaje konfiguracji (hibernate.properties lub hibernate.cfg.xml): Hibernate możemy skonfigurować na dwa sposoby: ● Plik właściwości hibernate.properties Jeśli właściwości zadeklaruje się w pliku hibernate.properties, wystarczy umieścić ten plik w ścieżce wyszukiwania klas aplikacji. Zostanie on automatycznie wykryty i wczytany przez Hibernate w momencie tworzenia obiektu Configuration. Przykładowy plik – Hibernate.properties hibernate.connection.driver_class=org.hsqldb.jdbcDriver hibernate.connection.url=jdbc:hsqldb:file:testdb hibernate.connection.username= user hibernate.connection.password= tajne hibernate.show_sql=false hibernate.dialect=org.hibernate.dialect.HSQLDialect hibernate.c3p0.min_size=5 hibernate.c3p0.max_size=20 hibernate.c3p0.timeout=300 (1) (2) (3) (4) (5) (1) Nazwa klasy Javy implementująca sterownik JDBC dla konkretnej bazy danych(klasa Driver).Plik jar sterownika musi znajdować się w ścieżce wyszukiwania klas. (2) Adres URL w formacie jdbc określający adres serwera i nazwę bazy danych dla połączeń JDBC (3)Minimalna liczba oczekujących na działania połączeń JDBC utrzymywanaprzez C3P0. (4)Maksymalna liczba połączeń w puli. Zostanie zgłoszony wyjątek wykonania, gdy wartość ta okaże się niewystarczająca. (5)Okres bezczynności podawany w sekundach, więc w przykładzie jest to 5 minut. Po tym okresie nieużywane połączenie zostaje usunięte z puli. Określanie właściwości w postaci hinernate.c3p0.* powoduje automatyczne wybranie biblioteki C3P0 jako rozwiązania puli połączeń dla Hibernate (nie potrzeba żadnej dodatkowej opcji w celu włączenia C3P0). Plik ten nie zawiera odwzorowań zasobów z pliku XML – i nie można zamieścić tej informacji w pliku właściwości. Jeżeli w ten sposób chcemy skonfigurować Hibernate, należy odwzorować klasy do obiektu Configuration w czasie wykonania: Configuration config = new Configuration(); config.addResource("Osoba.hbm.xml"); config.addClass( katalog.Osoba.class ); config.setProperties( System.getProperties() ); SessionFactory sessions = config.buildSessionFactory(); ● Plik konfiguracyjny hibernate.cfg.xml Konfiguracja poprzez plik hibernate.cfg.xml jest najczęściej wykorzystywanym sposobem. Wiele osób preferuje właśnie w ten sposób, by uniknąć dodawania parametrów do obiektu Configuration w kodzie aplikacji. Metoda odszukiwania pliku przez Hibernate polega na przeszukaniu CLASSPATH(ścieżki wyszukiwania klas). Podczas budowania projektu, należy pamiętać o umieszczeniu pliku hibernate.cfg.xml do odpowiedniego katalogu, który jest włączony do CLASSPATH. W odróżnieniu od pliku hibernate.properties, który zawiera tylko parametry konfiguracyjne plik hibernate.cfg.xml może również określać lokalizację plików odwzorowań. Na początku pliku występuje deklaracja typu dokumentu, która pomaga analizatorowi XML dokonać walidacji dokumentu, czyli sprawdzić zgodność jego formatu z wytycznymi zawartymi w DTD pliku konfiguracyjnego Hibernate. Konfiguracja Hibernate jest zawarta w elemencie głównym <hibernate-configuration>. Natomiast parametry konfiguracyjne dla konkretnej bazy danych są zawarte w elemencie <session-factory>. Parametry połączenia z bazą danych są zawarte w poszczególnych elementach <property>, a pliki z opisem odwzorowania klas Java na tabele w bazie danych wskazane w elementach <mapping>.Ważnym parametrem jest wskazanie dialektu SQL dla wykorzystywanej bazy danych (w przykładzie użyty jest dialekt bazy danych Oracle). Ustawienie tego parametru powoduje przyjęcie domyślnych wartości wielu innych parametrów, odpowiednich dla danej bazy danych. Hibernate SQL Dialects (hibernate.dialect): Nazwa: ● DB2 ● DB2AS/400 ● DB2OS390 ● PostgreSQL ● MySQL ● MySQLwithInnoDB ● MySQLwithMyISAM ● Oracle(anyversion) ● Oracle9i/10g ● Sybase ● SybaseAnywhere ● MicrosoftSQLServer ● SAPDB ● Informix ● HypersonicSQL ● Ingres ● Progress Dialect: org.hibernate.dialect.DB2Dialect org.hibernate.dialect.DB2400Dialect org.hibernate.dialect.DB2390Dialect org.hibernate.dialect.PostgreSQLDialect org.hibernate.dialect.MySQLDialect org.hibernate.dialect.MySQLInnoDBDialect org.hibernate.dialect.MySQLMyISAMDialect org.hibernate.dialect.OracleDialect org.hibernate.dialect.Oracle9Dialect org.hibernate.dialect.SybaseDialect org.hibernate.dialect.SybaseAnywhereDialect org.hibernate.dialect.SQLServerDialect org.hibernate.dialect.SAPDBDialect org.hibernate.dialect.InformixDialect org.hibernate.dialect.HSQLDialect org.hibernate.dialect.IngresDialect org.hibernate.dialect.ProgressDialect ● ● ● ● ● MckoiSQL Interbase Pointbase FrontBase Firebird org.hibernate.dialect.MckoiDialect org.hibernate.dialect.InterbaseDialect org.hibernate.dialect.PointbaseDialect org.hibernate.dialect.FrontbaseDialect org.hibernate.dialect.FirebirdDialect Przykładowy plik – Hibernate.cfg.xml <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> <property name="connection.url">jdbc:hsqldb:</property> <property name="connection.username">user</property> <property name="connection.password">tajne</property> <property name="dialect">org.hibernate.dialect.HSQLDialect</property> <property name="show_sql">true</property> <property name="hbm2ddl.auto">create</property> <mapping resource="Osoba.hbm.xml"/> </session-factory> </hibernate-configuration> Połączenia z bazą danych możemy określać poprzez DriverManager: <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> lub też poprzez DataSource podając źródło danych: <property name="connection.datasource">jdbc:hsqldb/nazwa_bd</property> Parametr <show_sql> jest parametrem, na który należy kłaść duży nacisk. Przydaje on się wielokrotnie, gdy tworzy się oprogramowanie. Ustawienie właściwości hibernate.show_sql na wartość true włącza wyświetlanie wszystkich utworzonych poleceń SQL na konsoli. Warto korzystać z tej opcji w momencie poszukiwania błędów, poszukiwania wąskiego gardła lub po prostu analizy działania Hibernate. Z kolei parametr <hbm2ddl.auto> umożliwia tworzenie struktury bazy danych przy starcie aplikacji (gdy hbm2ddl.auto wynosi create). Po przetestowaniu aplikacji parametr hbm2ddl.auto zaleca się ustawić na validate, albo po prostu wyłączyć poprzez usunięcie, czy zakomentowanie linii zawierającej parametr. Do inicjalizacji Hibernate wystarczy teraz użycie następującego kodu: SessionFactory sessions = new Configuration().configure().buildSessionFactory(); W momencie wywołania metody configure() Hibernate poszukuje pliku o nazwie hibernate.cfg.xml w ścieżce wyszukiwania klas. Jeśli chce się dla pliku konfiguracyjnego zastosować inną nazwę lub też plik znajduje się w niestandardowej lokalizacji, do metody configure() należy przekazać ścieżkę . SessionFactory sessions = new Configuration().configure("/hibernate-config/przyklad.cfg.xml") .buildSessionFactory(); Jeśli ścieżka wyszukiwania klas zawiera pliki hibernate.properties i hibernate.cfg.xml, ustawienia występujące w pliku XML nadpiszą ustawienia z pliku właściwości. Rodzaje zapytań w Hibernate ● Zapytania w języku HQL (Hibernate QueryLanguage): – składnia podobna do SQL – zorientowany obiektowo – zapytania odwołują się do klas, a nie tabel Podstawowym sposobem odczytu danych z bazy danych w Hibernate są zapytania formułowane w języku HQL (Hibernate Query Language). Język ten pod względem składni bardzo przypomina SQL. Podstawowa struktura zapytania to SELECT-FROM-WHERE-GROUP BY-HAVINGORDER BY, z tym że klauzula SELECT jest opcjonalna, gdy mają by odczytane całe obiekty. HQL w odróżnieniu od SQL jest zorientowany obiektowo i operuje na klasach języka Java,a nie tabelach relacyjnej bazy danych. HQL obsługuje dziedziczenie,polimorfizm i asocjacje między klasami. 1) wykonanie zapytań poprzez list() - zwraca cały wynik zapytania do kolekcji w pamięci (instancje pozostają w stanie trwałym): List osoby= (List) session.createQuery("from Osoba as osoba where osoba.imie= 'Zbigniew'").list(); for (int i=0; i<osoby.size(); i++) System.out.println(((Osoba)osoby.get(i)).getImie()); List osoby = (List)s.createQuery("select Osoba.id, Osoba.imie from Osoba as osoba where Osoba.nazwisko = 'Boniek'").list(); for (int i=0; i<osoby.size(); i++) { Object [ ] tab = (Object [ ]) osoby.get(i); System.out.println(tab[0]+" "+tab[1]); } 2) wykonywanie zapytań poprzez iterate() - zwraca wynik w kilku zapytaniach SELECT: -Pobiera identyfikatory -Oddzielne zapytania pobierające poszczególne instancje -Może być efektywniejsze od list(), gdy instancje już są w pamięci podręcznej, ale rozwiązanie to jest zazwyczaj wolniejsze. Iterator osoby = s.createQuery("from Osoba as osoba where osoba.nazwisko = 'Boniek'").iterate(); while (osoby.hasNext()) { Osoba o = (Osoba) osoby.next(); System.out.println(o.getImie()); } 3) Pobieranie obiektów z bazy danych: Domyślnie w przypadku operacji połączenia, HQL nie pobiera natychmiast związanych obiektów i kolekcji.Są one pobierane gdy nastąpi do nich pierwsze odwołanie (tryb „lazy fetching”).Dla wszystkich typów zbiorów, poza tablicami, można włączyć późne ładowanie wykorzystując zapis lazy = "true". Zastosowanie późnego ładowania umożliwia znaczne przyspieszenie szybkości działania aplikacji, szczególnie takich operacji, jak wyszukiwanie. Domyślnie tryb lazy jest ustawiony dla relacji @OneToMany i @ManyToMany. Pobranie encji następuje dopiero przy próbie odwołania się do niej, np odczyt z kolekcji. Leniwe (opóźnione )pobieranie pól obiektów może być przydatne tylko wtedy, gdy mamy w nich dużo danych i chcemy zaoszczędzić pracy zbieraczowi nieużytków (garbage collection). Z kolei dla relacji @OnetoOne oraz @ManytoOne domyślnym rozwiązaniem jest tzw. “eager fetching” czyli natychmiastowe ładowanie.HQL ignoruje ewentualne ustawienia podane przy odwzorowaniu.Stanowi to problem, gdy odwołanie do dowiązanego obiektu lub kolekcji nastąpi po zamknięciu sesji, w której wykonano zapytanie.Rozwiązaniem jest zastosowanie klauzul (działają dla list()): - INNER JOIN FETCH - dla pobrania pojedynczych obiektów LEFT JOIN FETCH - dla pobrania kolekcji from Dept as dept left join fetch dept.emps as emp from Emp as emp inner join fetch emp.dept ● ● ● Zapytania w natywnym SQL, dające możliwość wykorzystania specyficznych dla danego systemu konstrukcji składniowych np. CONNECT w Oracle. Zapytania poprzez obiekty Criteria, umożliwiające budowę zapytań poprzez obiektowe API Zapytania poprzez obiekty Example, umożliwiające wyszukiwanie danych w oparciu o przykadową instancję (mechanizm QBE – QueryByExample). Reverse Engineering (podejście “z dołu do góry”) W podejściu z dołu do góry tworzenie oprogramowania rozpoczyna się od istniejącego schematu bazy danych i modelu danych. W tym przypadku najwygodniej jest najpierw skorzystać z narzędzia Middlegen do wygenerowania plików odwzorowań Hibernate na podstawie istniejącej bazy danych, a następnie uruchomić narzędzie hbm2java do wygenerowania szkieletu klas trwałych POJO. Narzędzie hbm2java stosuje się do: generowania kodu źródłowego klas POJO na podstawie plików odwzorowań Hibernate przy podejściu od środka, generowania kodu źródłowego klas POJO na podstawie plików odwzorowań Hibernate uzyskanych automatycznie narzędziem Middlegen z istniejącego schematu bazy danych. Narzędzie można dostosować do własnych potrzeb, korzystając na przykład z dodatkowych metadanych (jak to miało miejsce w narzędziu hbm2ddl). Najczęściej wygenerowane pliki i klasy trzeba poprawiać i rozszerzać ręcznie, ponieważ nie wszystkie asocjacje klas i szczegóły metadanych można określić automatycznie na podstawie schematu bazy danych.