#13.1 – Baza danych z JPA cz. 1

By 21 listopada 2014Kurs Javy
Wpis-Header (7)

W dzisiejszej i jutrzejszej lekcji omówimy podstawy JPA – standardu, który znacznie upraszcza obsługę bazy danych z poziomu aplikacji. W pierwszej części lekcji zobaczymy sam JPA, w kolejnej zaś nauczymy się używać go we własnym projekcie.

JPA to standard z grupy tzw. ORM (ang. Object-Relational Mapping), co w wolnym tłumaczeniu oznacza mapowanie z modelu obiektowego (takiego, jaki używamy w aplikacjach Java) do modelu relacyjnego (takiego, z jakiego korzystają bazy danych). Oczywiście założeniem jest, żeby pracę programisty uprościć, a nie utrudnić, dlatego starano się zautomatyzować jak najwięcej rzeczy.

Lekcja

Aby korzystać z JPA będziemy potrzebowali tzw. providera – biblioteki, która dostarcza implementację standardu JPA (JPA to tylko zbiór kontraktów, tzn. opisów, jakie funkcje mają być realizowane i w jaki sposób – sami możemy wybrać lub stworzyć implementację, która będzie się tym zajmowała)

Tak naprawdę aby korzystać z JPA będziemy potrzebowali jednej adnotacji na każdej klasie oraz jednej na jej polu (identyfikatorze), którą chcemy też przechowywać w bazie danych (tak, jednej!). Jednocześnie możemy (i będziemy) modyfikować domyślne zachowanie JPA za pomocą dodatkowych adnotacji aby powiedzieć dokładnie, co chcemy, i w jaki sposób, żeby było zrobione. To kolejny przykład elastyczności i wygody, którą dostajemy dzięki podejściu convention-over-configuration.

Adnotacje w JPA

W przypadku baz danych (jak pewnie pamiętacie z ostatniej lekcji) mówimy o encjach i tak najważniejszą adnotacją w przypadku JPA jest właśnie @Entity. Adnotacja ta nie wymaga żadnych parametrów (możemy wybrać nazwę – pewnego rodzaju alias, używany później w zapytaniach, ale zostaniemy przy domyślnym – nazwie klasy), umieszczamy ją nad całą klasą, która ma być mapowana:

@Entity
public class Kot {
    @Id
    private Long id;

    //...
}

Klasa taka, którą adnotujemy za pomocą @Entity, musi mieć publiczne gettery i settery dla każdego pola. Założenia przyjęte przez JPA w takiej sytuacji są następujące:

  • tabela nazywa się tak jak klasa
  • kolumny nazywają się tak, jak pola i są odpowiedniego typu z możliwością wpisania null

Powyżej widzimy także adnotację @Id – wskazuje nam ona, że dane pole jest identyfikatorem (unikalnym) tego obiektu. Najczęściej jest to pole typu Long o nazwie id lub podobnej. Bardzo często można spotkać się z adnotacją @GeneratedValue, co wskazuje, że identyfikator ten jest generowany automatycznie w momencie zapisu do bazy danych.

Zmiana parametrów tabeli

Bardzo często mamy już gotową bazę danych, którą chcemy zmapować i nie chcąc zmieniać jej struktury musimy dopasować nasze mapowanie (np. nazwę tabeli). Służy do tego adnotacja @Table – najczęściej wykorzystujemy jej parametr name, jak na poniższym przykładzie:

@Entity
@Table(name="koty")
public class Kot {
    //...
}

Taki zapis spowoduje, że klasa Kot będzie mapowana na tabelę ‚koty’ zamiast domyślnej tabeli ‚Kot’

Zmiana parametrów kolumny

Podobnie jak w przypadku tabeli, domyślną nazwą kolumny jest nazwa pola. Najczęściej jednak nie jest to zgodne z konwencją nazewnictwa w bazie danych (gdzie często zamiast camelCase używamy nazw_z_podkreslnikami). Aby zmienić nazwę kolumny lub zmodyfikować jej atrybuty możemy użyć adnotacji @Column  jak na przykładzie poniżej:

@Entity
@Table(name="koty")
public class Kot {

    @Column(name="imie_kota", nullable=false)
    private String imieKota;
    //...
}

Widzimy tutaj dwie rzeczy: po pierwsze wskazujemy, że pole imieKota będzie zapisywane w kolumnie o nazwie imie_kota, na dodatek nie może mieć wartości NULL (ustawiliśmy atrybut nullable na false [domyślnie jest true] – jeśli spróbujemy zapisać w bazie danych obiekt, który w tym polu będzie miał wartość null, otrzymamy błąd bazy danych.

Uwaga! Bardzo często będziemy mieli do wyboru dwie adnotacje o takiej samej nazwie, np:

Jest to spowodowane tym, że framework dodaje nowe możliwości i opcje. Zasadniczo, o ile nie potrzebujemy naprawdę czegoś, co oferuje biblioteka, powinniśmy używać adnotacji z pakietu javax.persistence, ponieważ są to adnotacje standardowe JPA – korzystanie tylko z tych adnotacji pozwoli w przyszłości podmienić implementację JPA tylko za pomocą konfiguracji.
To ogólna zasada, o ile możliwe, powinniśmy korzystać ze standardów a nie z ich rozszerzeń zawsze, kiedy jest to możliwe.

EntityManager

EntityManager to standardowy sposób wykonywania operacji na obiektach w standardzie JPA. Jak zobaczymy w drugiej części lekcji, skonfigurujemy fabrykę (czyli sposób, w jaki będą automatycznie tworzone EntityManagery), dzięki czemu będziemy mogli używać EntityManagerów w naszych klasach.
Na dzisiaj ważne jest, żeby pamiętać o tym, że EntityManager to nasz ‚most’ pomiędzy bazą danych a naszą aplikacją.

EntityManager vs Session

Powód, dla którego poruszamy dzisiaj ten temat (o tym, jak go używać powiemy sobie jutro) jest związany z alternatywnym sposobem, z którym możemy się spotkać w wielu tutorialach, a mianowicie Sesją (SessionFactory i Session). Role Session oraz EntityManager są podobne, Session ma nieco większe możliwości, ale EntityManager jest częścią standardu (Session nie). Ma tu zastosowanie zasada, o której wspominałem wcześniej – jeśli nie potrzebujemy koniecznie funkcjonalności Session, używajmy standardów – EntityManagera. Szerzej na ten temat wypowiedział się Emmanuel Bernard, Architekt w zespole pracującym nad Hibernate w krótkim wywiadzie , podsumowując w skrócie można posłużyć się cytatem: „We encourage people to use the EntityManager” .

W drugiej części

W drugiej części (już jutro) zobaczymy, jak dołączyć JPA do naszego projektu i Springa oraz jak korzystać z JPA w naszych klasach DAO.

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przydatną, mamy małą prośbę: polub nasz fanpage. Dzięki temu będziesz zawsze na bieżąco z nowymi treściami na blogu ( i oczywiście, z nowymi częściami kursu Javy). Dzięki!

  •  
  •  
  •  
  •  
  •  
  • Jakub Zysnarski

    Annotacje, których używacie, są w pakiecie javax.persistence. Hibernate – w pakiecie org.hibernate.annotations posiada inne – np. @Entity od wersji 4.0 jest jako deprecated. Jaka jest różnica, których annotacji użyjemy?

    • Pakiet javax.persistence jest częścią standardu JPA. Hibernate miał własne ‚kopie’ adnotacji z uwagi na to, że rozszerzał ich możliwości. Obecnie te dodatkowe funkcje są przenoszone do zewnętrznych adnotacji czy konfiguracji, przez co adnotacje standardowe w pakietach hibernate stają się zbędne. Dokumentacja także zaleca korzystanie z tych dostępnych w pakiecie javax.persistence. Więcej informacji z linkami znajdziesz też na stackoverflow: http://stackoverflow.com/a/10352698/1563204 (właściwie całe pytanie tego dotyczy)

  • Dzięki, poprawione!

  • Krzysztof Ambroziak

    Cześć

    W trakcie lektury zarówno tego bloga jak i specyfikacji MySQL 5.7 i przykładów w sieci. Zauważyłem kilka rzeczy niewytłumaczonych, których nie rozumiem. Mam 3 pytania odnośnie baz danych:
    PYTANIE 1. Wiedząc, że Java nie posiada dla typów liczbowych specyfikatora unsigned, który występuje w bazach danych np. MySQL, jak lepiej tworzyć tabele z liczbową kolumną klucza głównego, która jest dodatkowo auto inkrementowana? Tworzyć ją tak:
    CREATE TABLE user (id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, …)
    i mieć możliwość wstawienia aż 4294967295 rekordów. Czy tak:
    CREATE TABLE user (id INT AUTO_INCREMENT PRIMARY KEY, …)
    Ujemne wartości dla takiej kolumny auto inkrementowanego klucza głównego da się wstawić ale ręcznie bo kolumna zdefiniowana jako INT AUTO_INCREMENT z kluczem PRIMARY KEY i tak zacznie numerować rekordy od wartości 1.
    PYTANIE 2. Jak mapować typy całkowitoliczbowe UNSIGNED w tabelach w bazie na typy całkowitoliczbowe w klasach. Czy użyć najmniejszego typu Javy który może pomieścić dany typ w bazie danych? Np. czy jeśli w jakiejś tabeli pewna kolumna jest typu TINYINT UNSIGNED, to w klasie odpowiadać jej ma pole typu short?
    PYTANIE 3. Zauważyłem, że podaliście @Id private Long id; Czy to jest jakaś konwencja by typy liczbowe podawać jako typ obiektowy „Long” zamiast nieobiektowego „long”? Poza tym zauważyłem w innych przykładach, często podaje się w klasie właśnie typ Long. A jeśli jakaś tabela przechowywałaby, przykładowo informacje o pierwiastkach chemicznych, których jest póki co 118, to też trzeba by podać typ Long? Typ Byte (nawet ze znakiem) wystarczyłby w zupełności.

    • Cześć, odpowiedając na pytania:
      1) Java ma typ ogólnoliczbowy BigDecimal i BigInteger, które mogą mieć dowolną precyzję – JPA pozwala mapować także na takie typy, nie ma więc przeszkód, aby stosować modyfikator unsigned na bazie danych. Zwróć uwagę, że także od definicji kolumny zależy faktyczny zakres liczby. W większości przypadków rozważanie czy 2 miliardy rekordów wystarczą czy nie jest jednak nieco na wyrost ;) zakres 2 miliardów w zupełności wystarczy, jeśli nie – zawsze możesz użyć typu BIGINT i Long w Javie
      2) Tak jak wspomniałem powyżej możesz użyć BigInteger, z pełnymi tego konsekwencjami. Najczęstszą praktyką jest jednak mapowanie, które podąża za domyślnym mapowaniem używanej bazy danych, w przypadku MySQL zerknij tutaj: https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-type-conversions.html (tabela 5.2). W przypadku kolumny typu INT widnieje tam informacja: „java.lang.Integer, if UNSIGNED java.lang.Long”
      3) Korzystanie z typów Integer/Long jest po prostu wygodne – większość operacji przyjmuje i zwraca tego rodzaju dane, więc nie jest wymagana ich konwersja. Z punktu widzenia optymalizacji mówimy o 8 bajtach w przypadku typu Long – mając na uwadze pojemności nośników i rozmiary pamięci aktualnie dostępne (a w Javie także pulę obiektów-liczb), w większości aplikacji stosowanie typu Long nie ma żadnego praktycznego wpływu na działanie aplikacji a jest po prostu wygodniejsze w kontekście używania.
      Co do Long vs long – prymitywy mają domyślną wartość 0 (w przypadku liczb), co może prowadzić do przeoczeń – dobrą praktyką jest używanie obiektów, o ile nie ma szczególnych wymagań sugerujących inne podejście

      Mam nadzieję że to pomoże trochę rozwiać wątpliwości :)

      • Krzysztof Ambroziak

        Tak dziękuję, i to bardzo pomogłeś. Czyli jeśli dobrze zrozumiałem, to projektując bazę danych i poszczególne tabele, najlepiej od razu ustawiać kolumny klucza głównego na INT lub LONG (bez UNSIGNED) bo w javowej aplikacji backendowej jeśli będą innego typu numerycznego to i tak zostaną gdzieś przekonwertowane albo na Integer albo na Long, zgadza się? Czyli nie ma potrzeby oszczędzać bajtów na typy TINYINT, SMALLINT czy MEDIUMINT w tabelach?