Monthly Archives: March 2013

ORM – Relacje

Bardzo wiele osób utożsamia JPA z używaniem ORM. Jest to zasadniczo wcale nie aż tak głupie myślenie – przy prostych projektach bez większych problemów poradzić można sobie tworząc kilka klas domenowych oraz odwołując się do nich prostymi metodami z EntityManagera. By jednakże w pełni to osiągnąć trzeba poruszyć bardzo ważny aspekt ORM – relacje.

Współczesne bazy danych nie od biedy nazywane są relacyjnymi – dane przechowywane są w oddzielnych tabelach, kluczowe zaś są relacje jakie zachodzą między nimi. Zrozumienie podstaw ORM jest dość łatwe – opanowanie arkanów mapowania relacji oraz tego w jaki sposób będą się one zachować jest cokolwiek trudniejsze. Dlatego też temu zagadnieniu poświęcę dzisiejszy wpis. By jednakże w spokoju pracować nad relacjami potrzebujemy kolejnych obiektów domenowych, aby móc się do nich odwoływać. Przykładową encją niech będzie smok (a co, każdy bohater może mieć smoka):

@Entity
public class Dragon {

    @Id
    @GeneratedValue(generator="increment")
    @GenericGenerator(name="increment", strategy = "increment")
    private Long id;

    private String name;

    public Dragon() { }

    // Gettery i settery pominiete
}

W podobny sposób trzeba zadeklarować kilka obiektów, które wyglądają dokładnie tak samo, tylko mają inną nazwę. Dodamy zatem jeszcze Weapon (sztuka broni) oraz Deity (bóstwo). Dzięki temu tłumaczenie relacji będzie delikatnie mówiąc – łatwiejsze.

Relacje one-to-one

Zasadniczo są one najłatwiejsze do zrozumienia zatem od nich zaczniemy. Relacja tego typu zakłada sytuację, w której dokładnie jednej encji odpowiada dokładnie jedna inna encja. Przykładem z życia jest choćby człowiek i jego numer PESEL – każdy człowiek ma jeden numer pesel, każdy pesel reprezentuje jednego człowieka – tertium non datur. Na przykładzie naszej gry będzie to relacja pomiędzy naszym bohaterem i smokiem (pomysł pochodzi z serii Eragon). Każdy bohater ma dokładnie jednego smoka, nikt inny tego smoka dosiąść nie może. Co więcej – jak smoka ubiją (lub się nażre czegoś, potem wypije Wisłę wody i pęknie) nasz bohater jest skazany na konie i własne nogi. Perfekcyjna relacja one-to-one. Naszą bohaterską encję musimy rozszerzyć o obiekt ze smokiem.

@OneToOne
@JoinColumn(name="dragon_id")
private Dragon dragon;

Tadam, mamy smoka 😉 Adnotacje omówię za chwilkę – naszego smoka dodajemy do naszego bohatera w ten sposób.

Dragon d = new Dragon();
d.setName("Smok Wawelski");
entityManager.persist( d );
hero.setDragon(d);

Jak widać dodanie relacji do encji jest banalnie proste. Wyjaśnienia wymagają z całą pewnością użyte adnotacje. @OneToOne należy do grupy adnotacji, które pojawiają się zawsze przy mapowaniu relacji (inne nazywają się równie obrazowo). Zalecam zapoznanie się z dokumentacją dotyczącą atrybutów tej adnotacji – na przykładzie powyżej pozostawiłem wszystkie wartości domyślne i zasadniczo działa to dobrze. Akurat twórcy JPA pomyśleli sensownie w tym przypadku.

Drugą istotną adnotacją jest @JoinColumn. Zgodnie z nazwą określa ona jaka kolumna (no i z jakimi atrybutami) będzie odwzorowywać relację. W naszym przypadku nasz bohater jest stroną posiadającą (owning side) relację. W związku z tym będziemy trzymać w wygenerowanej tabeli z bohaterami referencję do tabeli ze smokami (klucz obcy). W powyższym przykładzie wskazałem nazwę kolumny – domyślnie jest ona tworzona z nazwy własności, podreślnika + kilka zasad – jak zawsze polecam zapoznanie się z dokumentacją by poznać szczegóły.

Relację, którą pokazałem na powyższym przykładzie możemy nazwać jednostronną (unidirectional). Nasz heros wie wszystko o swoim smoku, ale sam smok nie bardzo ma pojęcie o istnieniu naszego bohatera. Czyli zamiast grzecznie dać się ujeździć prędzej zeżre potencjalnego jeźdźca. Zresztą w opisie smoka wyszło nam jasno, że smok ma tylko jednego jeźdźca, zatem dobrze by było, aby coś o nim wiedział. Taką relację (gdzie obie strony wiedzą o sobie) nazywamy bidirectional.

@OneToOne(mappedBy="dragon")
private Hero rider;

Teraz nasz smok również ma wiedzę o drugiej stronie relacji. W przypadku bazy danych nie zmieniło się nic – jednakże nie ma problemu by wykonać następujący kod:

Dragon dragon = entityManager.find(Dragon.class, 1L);
Hero rider = dragon.getRider();
System.out.println("Jezdziec nazywa sie " + rider.getName() );

U mnie jeźdźcem jest ‘Chlebikowy mag’ 😉

Relacje many-to-one i one-to-many

Wspomniałem na samym początku by utworzyć też encję reprezentującą broń (Weapon). Zasadniczo zgodzimy się, że każdy bohater może mieć kilkanaście sztuk broni (jakiś mieczyk, sztylet, może i łuk dla odmiany). Mamy zatem kilkanaście sztuk konkretnego przedmiotu, które przynależą do jednej encji (naszego bohatera). Podręcznikowy zatem przykład relacji many-to-one. W jej przypadku to strona many jest ‘posiadaczem’ relacji, gdyż to w niej będzie zapisany klucz obcy do encji bohatera. Ma to zasadniczo sens – każda sztuka broni trzyma informację o swoim właścicielu. Sam zaś bohater (na poziomie tabeli w bazie danych) nie ma o broni pojęcia. W klasie broni dodajemy zatem taki kod:

@ManyToOne
private Hero owner;
// Getterki i setterki pominięte

Tym samym każda sztuka broni posiada referencję do swojego posiadacza. Jak już wspomniałem w przypadku takiej relacji to strona obdarzona adnotacją @ManyToOne jest stroną posiadającą. Dzięki temu w tabeli Weapon będzie składowana kolumna z kluczem obcym do encji bohaterów. Przy domyślnym zachowaniu adnotacji – zostanie ona wygenerowana z nazwy własności encji oraz nazwy kolumny z kluczem głównym w docelowej encji (czyli u nas będzie to kolumna owner_id w tabeli Weapon). Podobnie jak w przypadku poprzednich relacji możemy sterować tą relacją za pomocą adnotacji @JoinColumn. Moglibyśmy zatem nasz kod zmodyfikować by wyglądał w ten sposób:

@ManyToOne
@JoinColumn(name="hero_id")
private Hero owner;

Co sprawia, że przynajmniej na poziomie bazy danych nasza tabela jest trochę bardziej jednoznaczna i czytelna. W powyższym kodzie powtarzamy jednak sytuację z bohaterem i jego smokiem. Z całą pewnością dobrze by było, aby bohater wiedział jaką broń ma do dyspozycji. Wtedy będzie to niejako odwrócenie relacji many-to-one, czyli będziemy mieć do czynienia z relacją typu one-to-many. Zmodyfikujemy zatem klasę Hero.

@OneToMany(mappedBy="owner")
private List weapons;

Jak już wspomniałem bohater jest w tej stronie stroną podrzędną w relacji (inverse side) – jedynym zatem elementem poza adnotacją @OneToMany jest wskazanie na własność, która jest posiadającą relację w klasie Weapon (czyli na owner). Istnieje jednakże możliwość, aby nie specyfikować atrybutu mappedBy. W tym przypadku zajdą zmiany na poziomie bazy danych (przy użyciu tego atrybutu tak naprawdę wszystko pozostaje po staremu) – powstanie tabela łącząca encję bohatera z bronią. Kod encji broni w tym przypadku nie posiadałby w ogóle informacji o właścicielu (należy usunąć własność owner), zaś encja bohatera powinna wyglądać tak:

@OneToMany
@JoinTable(name="HERO_WEAPON",
	joinColumns=@JoinColumn(name="HERO_ID"),
	inverseJoinColumns=@JoinColumn(name="WEAPON_ID"))
private List weapons;

W tym momencie powstaje tabela łącząca o nazwie HERO_WEAPON z nazwami kolumn jak podaliśmy w adnotacji @JoinTable. Na poziomie bazy danych encje biorące udział w relacji w ogóle nie wiedzą o swym istnieniu (podobnie jak w przypadku relacji many-to-many nie posiadają kolumn z kluczami obcymi w tabelach).

Relacje many-to-many

Ten typ relacji jest równie łatwy do zrozumienia co jeden do jednego. Relacja wiele-do-wielu zakłada istnienie tabeli pośredniczącej. W tabeli tej występują dwie kolumny – z parami kluczy obcych wskazującymi na encje znajdujące się w innych tabelach. Przykładem takiej relacji są bóstwa (w naszej grze RPG istnieje politeizm). Jedno bóstwo może mieć miliony wyznawców, ale też i każdy bohater może jednocześnie wyznawać kilka bóstw. Zaczniemy zatem od modyfikacji naszego bohatera:

@ManyToMany
List deities;

Jak i również trzeba w klasie bóstwa dorzucić wyznawców:

@ManyToMany
List believers;

I zasadniczo nie ma nic więcej do dodania. Jeśli uruchomimy kod aplikacji (z create-drop w persistence.xml), wówczas pojawi się w bazie danych ciekawa sytuacja – dwie tabele łączące! Dlaczego tak? W przypadku relacji wiele-do-wielu nie można (w sensie logicznym) wskazać właściciela tej relacji. Jednakże programista musi dokonać arbitralnego wyboru tej encji, która zostanie potraktowana jako właściciel relacji, zaś tym samym druga strona relacji staje się podrzędną. Załóżmy, że w powyższym przykładzie za posiadającą uznamy encję bóstwa (tak dla odmiany).

@ManyToMany
@JoinTable(name="DEITY_HERO",
	joinColumns=@JoinColumn(name="DEITY_ID"),
 	inverseJoinColumns=@JoinColumn(name="HERO_ID"))
List believers;

Bohater zaś jest elementem ‘podrzędnym’:

@ManyToMany(mappedBy="believers")
List deities;

Przy powyższym kodzie w bazie danych powstanie tylko jedna tabela łącząca zawierająca klucze obce. Więcej ciekawostek można znaleźć w dokumentacji adnotacji @ManyToMany

Sposoby pobierania danych

W zależności od typu relacji dane pobierane są przez JPA w różny sposób. Sensowniej będzie podeprzeć się cytatem z książki:

The fetch mode can be specified on any of the four relationship mapping types. When not specified on a single-valued relationship, the related object is guaranteed to be loaded eagerly. Collectionvalued relationships default to be lazily loaded, but because lazy loading is only a hint to the provider, they can be loaded eagerly if the provider decides to do so.

Dość często spotykanym podejściem jest specyfikowanie różnego sposobu pobierania (poprzez adnotację @FetchType) – kiedy pobieramy bohatera może nie jest dobrym od razu pobierać całą jego broń, a w późniejszym pewnie terminie całe drzekwo umiejętności, bóstw i tak dalej. Wtedy specyfikujemy relacje jako lazy-loaded. Jednakże kiedy pobieramy informacje o konkretnej sztuce broni – dobrze by było wiedzieć kto jest jej właścicielem. W sumie w końcu jak np. naostrzyć broń bez właściela – wszak ktoś musi za to zapłacić 😉

ORM – podstawy

W kolejnej części przygody z JPA zajmiemy się samym mięskiem – czyli mapowaniem relacyjno-obiektowym. W jaki sposób obiekty są odwzorowywane na bazę danych, jak się łączą (o relacjach będzie w kolejnym wpisie) oraz jak da się je jednoznacznie identyfikować. Temat dość długi i obszerny zatem do roboty.

Dostęp do pól encji oraz ich mapowanie

W poprzednim wpisie dość pobieżnie pokazałem kilka adnotacji używanych w encjach. Wystarczyło to, aby pokazać działający przykład. Teraz skupimy się troszkę bardziej dokładnie nad własnościami encji. JPA może używać własności obiektu na dwa sposoby – za pomocą dostępu do pól (field access) lub dostępu do własności (nie wiem czy dobrze to tłumaczę, w oryginale property access).

  • field access – dostęp providera bezpośrednio do pól encji. Nie są potrzebne gettery/settery, zaś jeśli występują są ignorowane. Pola encji nie powinny mieć dostępu publicznego (choć nie jest to wymóg JPA, ale dobra praktyka).
  • property access – przy uzyciu tego trybu dostępu encja musi spełniać kontrakt JavaBeans – posiadać odpowiednie gettery i settery. Typ pola jest określany na podstawie typu zwracanego przez getter, zaś metody dostępowe muszą rzecz jasna mieć poziom dostępu public lub protected.

Mógłbyś się zapytać czytelniku – co jest domyślnym sposobem dostępu? Ano tutaj sprawa jest dość prosta – wystarczy, że umieścisz adnotację @Id nad polem encji oznaczającym klucz główny i zostanie użyty dostęp field. Jeśli dokonasz tego na metodzie getId() włączy się property access. Innym sposobem jest użycie adnotacji @Access, którą można umieścić na poziomie klasy bądź pól/metod (wraz z jej własnościami – AccessType. Jest to nowość wprowadzona w wersji drugiej JPA. Nie ma zatem problemu by część własności była dostępna poprzez pola, a część poprzez gettery. Jednakże takie mieszanie nie jest dobre – zaciemnia kod. Może być przydatne w przypadku dodawania nowych pól w encjach dziedziczących po rodzicach (możemy nie mieć do tychże rodziców dostępu), bądź też chcąc dokonać jakiejś dodatkowej transformacji wartości.

Na sam koniec omawiania dostępu do własności encji należy wspomnieć o możliwości zablokowania zapisu danych do bazy. Służy do tego adnotacja @Transient. Różni się ona od działania słowa kluczowego transient w Javie, zaś najlepiej wyjaśnia to StackOverflow.

W pierwszym wpisie wspomniałem o adnotacji @Table. Najczęściej pozostawia się ją samodzielną – nie zmieniając atrybutów i zachowując domyślne wartości. Wyjątkiem może być nazwa docelowej tabeli, do której trafi dana encja. Służy do tego atrybut name. Z ciekawych atrybutów (radzę poczytać sobie JavaDoc dla wszystkich adnotacji, to naprawdę pomaga) można wspomnieć uniqueConstraints – dodatkowe ograniczenia dla tabeli jeśli chodzi o wartości unikalne (oczywiście chodzi o podanie nazw kolumn lub ich kombinacji).

Mapowanie kolumn i ich pobieranie

Tabelę już mamy – co zatem z jej kolumnami? Do tej pory zadowalaliśmy się domyślnymi wartościami dostarczanymi przez Hibernate – łancuchy tekstowe lądowały w naszej bazie jako VARCHAR, identyfikator był longiem, zaś data była datą ;). Bardzo często takie podejście wystarcza – jednakże w przypadku zastanego schematu bazy danych, bądź też mając na uwadzę wydajność dobrze jest zabrać sprawy w swoje ręce. Do operowania budową i własnościami kolumny użytej do zapisywania wartości encji służy adnotacja (a jakże) – @Column. Posiada ona istotne atrybuty i dobrze jest się z nimi zapoznać – do najbardziej popularnych należą choćby: nullable, length czy unique.

Co zrobić w przypadku kiedy dane w tabeli są ‘ciężkie’? Załóżmy, że posiadamy kolumnę z danymi binarnymi (np. obrazek). Załóżmy, że mógłby być to dowolny obrazek (mający nawet kilka MB), który ma przedstawiać naszego herosa w przykładowej grze RPG. Pole w naszej encji Hero mogłoby wyglądać tak:

 @Lob
 @Column(columnDefinition="CLOB NULL")
 private String heroImage;

Jednakże zgódźmy się – z całą pewnością o wiele częściej będziemy wyświetlać proste wartości naszego herosa – level czy imię. Cały (duży) obrazek o wiele rzadziej. Cóż zatem z tym zrobić? Zastosować lazy loading. Domyślnie wszystkie pola encji (poza tymi oznaczonymi jako transient) są opatrzone ‘niejawnie’ adnotacją @Basic. Jednym z jej atrybutów jest fetch, który przyjmuje wartości zdefiniowane w typie FetchType. Domyślnie użyta wartość to EAGER. Oznacza ona natychmiastowe (zachłanne) pobieranie wartości. Jednakże pobieranie za każdym razem z bazy danych potencjalnie dużej ilości danych mija się z celem. Tutaj z pomocą przychodzi nam drugi typ pobierania danych – LAZY (leniwy czy tez opóźniony). Dzięki adnotowaniu pola by jego wartość była pobierana w ten sposób, zostanie ona pobrana z bazy danych dopiero kiedy kod źródłowy odwoła się do jej wartości. W przypadku naszego pola wyglądałoby to tak:

@Lob
@Column(columnDefinition="CLOB NULL")
@Basic(fetch=FetchType.LAZY)
private String heroImage;

Nasz przykład jest dość ekstremalny (pliki tego typu raczej przechowuje się fizycznie na dysku), ale dobrze pokazuje samą ideę. Do tematu lazy-loadingu powrócę podczas omawiania mapowania relacji i kolekcji.

Jakie konkretnie typy zmiennych możemy używać w encjach? Sporo 😉 Lista jest dość rozbudowana (cytat z książki):

• Primitive Java types: byte, int, short, long, boolean, char, float, double
• Wrapper classes of primitive Java types: Byte, Integer, Short, Long, Boolean,
Character, Float, Double
• Byte and character array types: byte[], Byte[], char[], Character[]
• Large numeric types: java.math.BigInteger, java.math.BigDecimal
• Strings: java.lang.String
• Java temporal types: java.util.Date, java.util.Calendar
• JDBC temporal types: java.sql.Date, java.sql.Time, java.sql.Timestamp
• Enumerated types: Any system or user-defined enumerated type
• Serializable objects: Any system or user-defined serializable type

Typy wyliczeniowe

W tym miejscu chciałbym napisać kilka słów o ENUMach. Przyznam szczerze, iż bardzo lubię tę konstrukcję – zarówno w przypadku Javy jak i baz danych (natywnie choćby wspierany przez MySQL). Z całą pewnością typy wyliczeniowe są o wiele bardziej czytelne niż maski bitowe bądź po prostu czyste wartości INT wrzucone do bazy. Oczywiście w przypadku JPA głównie chcielibyśmy poruszać się w obrębie obiektów, a do bazy danych nawet nie zaglądać, użycie typów wyliczeniowych jest bardziej przejrzyste (zainteresowanych odsyłam do Effective Java Joshui Blocha). Wspomniałem powyżej, że posiłkowanie się czystymi INTami nie jest sensowne. Jest to jednakże domyślny sposób, w jaki typy wyliczeniowe są przechowywane w bazie danych. Załóżmy, że chcielibyśmy przerobić naszego bohatera w ten sposób, aby jego profesja była właśnie typem wyliczeniowym. Stwórzmy zatem taki typ:

package com.wordpress.chlebik.jpa.domain;

public enum Profession {
    MAGE, WARRIOR, THIEF, DRUID, ARCHER
}

Oraz oczywiśie zmodyfikujmy klasę domenową naszego bohatera:

private Profession profession;

I od teraz możemy tworzyć naszego bohatera nadając mu jedną z dostępnych profesji. Co się stanie w przypadku zapisu obiektu do bazy danych? Otóż domyślnie kolejne wartości typu wyliczeniowego otrzymają wartości INT – zaczynając od zera. W 99% przypadków absolutnie nam to wystarczy, zwłaszcza jeśli nie musimy np. wyświetlać wartości tej zmiennej użytkownikowi. To ORM bierze na siebie kwestię przerobienia numerka na obiekt. Problem pojawi się, jeśli nagle postanowimy dodać do naszego typu wyliczeniowego kolejną profesję, ale wepchniemy ją między już istniejące! Spowoduje to, że np. wojownik mający do tej pory numerek 1 nagle dostanie 2. Logika naszej aplikacji totalnie rozjedzie się z bazą danych – w związku z tym lepiej takiej operacji nie przeprowadzać! Jeśli już to dopisujmy kolejne wartości ‘na końcu’ wszystkich wartości – problemy mamy z głowy.

Co jednakże zrobić jeśli cyferki nam nie starczą? Dla przykładu – nasz typ wyliczeniowy prawdopodobnie będzie dość często zmieniany (sensowność stosowania wówczas ENUMa zostawiam na boku). Wówczas najlepiej nie zdawać się na domyślne zachowanie JPA, ale należy poinstruować mechanizm jak ma się zachowywać. Wówczas możemy wykorzystać adnotację @Enumerated wraz z jej wartością @EnumType. Adnotacja @Enumerated przyjmuje domyślnie wartość EnumType.ORDINAL, czyli mapujemy wartości na liczby. Istnieje też druga opcja – EnumType.STRING. Nazwa jest dość czytelna – nasze wartości typu wyliczeniowego zostaną w bazie zapisane nie w kolumnie numerycznej, ale tekstowej. Od tej pory nie boli nas w jakikolwiek sposób kolejność zadeklarowanych wartości w typie, a także przeglądając bazę danych (grubym klientem dla przykładu) widzimy od razu w czytelnej formie jaką profesję mają zapisani gracze.

Adnotacje czasu

W pierwszym wpisie cyklu pojawiła się adnotacja @Temporal. Służy ona poinstruowania JPA w jaki sposób ma zapisać jeden z obiektów daty pochodzącego z pakietu java.util.*. Standardowo bowiem w Javie obiektami, które ‘dotykają’ bazy danych w przypadku dat i czasu są reprezentacje klas z pakietu java.sql.*java.sql.Date, java.sql.Time oraz java.sql.Timestamp. Jeśli własności naszej encji będą jednym z tych typów wówczas nie ma problemu – zostaną w pełni zrozumiane przez JPA. W przypadku klas java.util.Calendar oraz java.util.Date nie jest już tak różowo. Używając adnotacji @Temporal wraz z nadaniem jej wartości za pomocą typu wyliczeniowego TemporalType określamy który z typów z pakietu java.sql.* zostanie użyty do mapowania konkretnego pola ( DATE, TIME, TIMESTAMP ). W przypadku braku tej adnotacji (przynajmniej z użyciem Hibernate i bazy H2) został użyty typ TIMESTAMP.

Mapowanie klucza głównego

Wspomniałem wcześniej, że posiadanie klucza głównego jest jednym z sine qua non bycia encją. W związku z tym dość istotnym jest, aby jednoznaczne określenie indywidualnej było proste, ale też dość elastyczne. Najprostszym (zasadniczo też chyba najbardziej rozpowszechnionym) sposobem mapowania klucza głównego jest jednoznacznie unikalna identyfikacja encji za pomocą wartości liczbowej. Póki co to właśnie do takiego sposobu ograniczaliśmy nasz kod. Wspomniałem jednakże o elastyczności – JPA zapewnia ją poprzez możliwość wykorzystania w charakterze klucza następujących typów Javy:

• Primitive Java types: byte, int, short, long, char
• Wrapper classes of primitive Java types: Byte, Integer, Short, Long, Character
• String: java.lang.String
• Large numeric type: java.math.BigInteger
• Temporal types: java.util.Date, java.sql.Date

Możliwe jest też używanie float, double bądź ich wrapperów, ale nie jest to oczywiście zalecane.

W pierwszym wpisie generowania identyfikatorów nie dokonywaliśmy ręcznie. Jest to dość zrozumiałe – w istniejących bazach danych mamy mniej lub bardziej zaawansowane mechanizmy generowania kolejnych wartości dla różnych typów. Z definicji jest to o wiele bardziej racjonalne podejście niż ręczne zapewnianie unikalności identyfikatorów. By wykorzystać możliwość baz danych musimy użyć adnotacji @GeneratedValue – jak sama nazwa wskazuje generowanie adnotowanych nią własności przejmie JPA. Nasza adnotacja posiada dwa atrybuty – generator oraz strategy. Pierwszy z nich ma wartość tekstową i określa po prostu nazwę używanego generatora (czyli np. sekwencji w przypadku wyboru takiej strategii generowania). Atrybut strategy ( typu wyliczeniowego GenerationType ) wskazuje na sposób w jaki będą generowane kolejne identyfikatory. Oto ich lista:

  • AUTO
  • – domyślna wartość, która wskazuje na to, że to JPA (konkretnie dostawca implementacji) powinno zadecydować o użytej strategii. Nie jest to do końca bezpieczne rozwiązanie, gdyż provider może zdecydować o wykorzystaniu zasobów, do których np. może nie mieć uprawnień (jak choćby tworzenie tabeli). Najlepiej sprawdza się przy testach i prototypowaniu.

  • SEQUENCE
  • – zakłada użycie istniejącej sekwencji dla generowania kolejnych wartości. W tym momencie wchodzi do gry adnotacja @SequenceGenerator. Jedynym obowiązkowym atrybutem jest nazwa generatora, sama nazwa użytej sekwencji domyślnie jest ustawiana przez dostawcę implementacji. Zatem bohaterowie w naszej grze mogliby mieć tak generowane id:

    @SequenceGenerator(name="heroesPKGenerator", sequenceName="SEQ_HEROES_ID")
    @Id @GeneratedValue(generator="heroesPKGenerator")
    private int id;
    
  • IDENTITY
  • – jeśli baza danych umożliwia wykorzystanie specjalnego typu kolumny (np. w MySQL to kolumny oznaczone atrybutem AUTO-INCREMENT) wówczas zostanie ona użyta. Taką strategię użyłem w swoim przykładzie. Jej zaletą jest prostota i brak dodatkowych obostrzeń (np. możliwość tworzenia sekwencji). Problemem jest brak przenoszalności takiego rozwiązania między niektórymi bazami, a także brak dostępności wygenerowanego ID przed zakończeniem transakcji.

    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;
    
  • TABLE
  • – zostanie stworzona oddzielna tabela w bazie danych dla przechowywania kolejnej dostępnej wartości dla klucza. Jest to zasadniczo najbardziej przenośna strategia – baza danych nie musi wspierać kolumn identyfikujących lub strategii, ale tworzenie tabel raczej tak 😉 Używając tej strategii tworzona jest oddzielna tabela, która zawiera dwie kolumny – w jednej podany jest identyfikator będący nazwą generatora, w drugiej zaś liczbowa wartość ostatnio użytego identyfikatora. W przypadku używania tej strategii najlepiej posiłkować się adnotacją @TableGenerator, której atrybuty umożliwiają sterowanie nazwą używanej tabeli, nazwami kolumn i szeregiem innych. Przykładowy kod mógłby wyglądać tak:

            @TableGenerator(
                name="heroesPKGenerator", 
                table="ID_GEN", 
                pkColumnName="GEN_KEY", 
                valueColumnName="GEN_VALUE", 
                pkColumnValue="ID", 
                allocationSize=1)
            @Id
            @GeneratedValue(strategy=TABLE, generator="heroesPKGenerator")
            private int id;
    

Obiekty wbudowane

Rzecz dotyczy bytów, które po angielsku nazywane są embedded objects. Są one obiektami, ale nie posiadają jednoznacznej identyfikacji – innymi słowy zaś – nie są encjami. Dane, które reprezentują są przechowywane w wierszu tabeli w bazie danych i jako takie stanowią składową encji. Myślę, że przykład z naszej gry RPG będzie bardziej wymowny. Nasza klasa bohatera zawiera kilka prostych wartości, które opisują postać. Jednakże wraz ze wzrostem ilości informacji rośnie nam stopień komplikacji encji jako obiektu Javy. Gdybym miał operować na obiekcie Javy, który posiada 20 składowych podrapałbym się po głowie i stwierdził krótko, że coś tutaj z software craftsmanship nie do końca po drodze. Temu właśnie służą embedded objects – do grupowania logicznie powiązanych właściwości.

Z całą pewnością gracze w RPG powinni posiadać jakąś gotówkę. Jednakże w realiach RPG płacenie za bułkę czystym złotem niespecjalnie ma sens. Moglibyśmy zatem wprowadzić rozróżnienie na 3 typy monet w zależności od kruszczu, z którego są wykonane. Mielibyśmy zatem monety miedziane, srebrne i złote.

private int cooper;
private int silver;
private int gold;

Klasa Hero już posiada dokładnie 5 własności. Dodanie tak prostej informacji jak ilość posiadanych pieniędzy niemal podwaja ich ilość. Jednocześnie zaś informacje o posiadanych środkach finansowych zostaną zapisane w bazie zaraz obok imienia czy profesji. Aby nasza klasa jako byt programistyczny wyglądała troszeczkę bardziej sensownie możemy zastosować klasę wbudowaną. Kod wyglądałby tak:

@Embeddable
public class FinanceState {
	
	private int cooper;
	private int silver;
	private int gold;
	
        // Gettery i settery uciete
}

Klasę Hero obdarowujemy nową własnością.

@Embedded private FinanceState finance;

Zaś podczas tworzenia bohatera dorzucamy taki kod:

FinanceState finances = new FinanceState();
finances.setCooper(10);
finances.setSilver(20);
finances.setGold(1);
h.setFinance(finances);

Czyż nie jest czyściej? W dodatku do tego obiekty klasy FinanceState mogą posiadać użyteczne metody użytkowe (czy stać mnie na zakup towaru? czy mogę wymienić np. 20 sreber na 1 złoto?). Jeśli uruchomimy teraz naszą testową aplikację w bazie danych tabela Heroes zostanie rozszerzona o 3 kolumny z ilością posiadanych monet. Używanie obiektów wbudowanych ma jeszcze jedną zaletę – jeśli zdarzy się nam sytuacja, w której potrzebowalibyśmy podobnego rozwiązania (np. w naszej grze do zapisu informacji o przeciwnikach – ile monet jakiego typu będziemy mogli uzyskać kiedy takowego deilkwenta poślemy do piachu), klasę wbudowaną możemy bez problemu użyć raz jeszcze.

Reużywalność obiektów wbudowanych może napotkać pewne problemy. Wyobraźmy sobie dopiero co wspomnianą sytuację – nasz bohater posiada kolumny nazwane dokładnie tak samo jak własności w klasie FinanceState. Jednakże tabela z wrogami została dokoptowana z innego projektu/gry i konkretne kolumny mają nazwy skrócone – co, si, gl. Mamy problem. Jednakże da się go obejść za pomocą adnotacji @AttributeOverride oraz @AttributeOverrides. Umożliwiają one dla konkretnej klasy nadpisać wartości nazw kolumn obiektu wbudowanego. Przykładowo klasa reprezentująca konkretnego wroga problem finansów mogłaby rozwiązywać w ten sposób:

    @Embedded
    @AttributeOverrides({
	    @AttributeOverride(name="cooper", column=@Column(name="co")),
	    @AttributeOverride(name="silver", column=@Column(name="si")),
	    @AttributeOverride(name="gold", column=@Column(name="gl")),
    })
    private FinanceState finance;

To tyle na dziś. W następnym artykule przypatrzymy się bliżej relacjom.