18 maja 2009

Z rozdziału 17. o Hibernate i istniejących konfiguracjach bazodanowych w Grails z DGG2

Dotychczasowe przedstawienie cech Grails w książce "The Definitive Guide to Grails, Second Edition" dotyczyło obsługi bazy danych, którą tworzono na potrzeby aplikacji, bez wcześniejszych ograniczeń nakładanych przez istniejącą architekturę. Taki stan autorzy określają jako "a green field application" (patrz sekcja dotycząca inżynierii programowania). Zero ograniczeń, cudo projekt. W rzeczywistości jednak możemy zapomnieć o takich projektach, bo one istnieją jak istnieje najwyższa wygrana w TOTKA, która będzie należała do Ciebie :)

W rozdziale 17. "Legacy Integration with Hibernate" autorzy przedstawiają możliwości, jakie udostępnia ORM DSL w Grails, którego zadaniem jest uproszczenie konfiguracji Hibernate, a tym samym współpracy z istniejącymi konfiguracjami baz danych. To jest sytuacja, gdzie nie wystarczy powiedzieć, że potrafimy korzystać z bazy danych w aplikacji grailsowej, ale potrzeba wykazać się wiedzą jak dopasować aplikację do już działającej bazy danych (potencjalnie z pewnymi danymi dostępnymi jedynie w trybie do odczytu).

Temat był już omawiany w dedykowanym ORM DSL rozdziale 10. "Grails ORM" z moimi relacjami w Z rozdziału 10. o Grails ORM (GORM) z DGG2 oraz Dokończenie rozdziału 10. o Grails ORM (GORM) z DGG2.

Konfiguracja odwzorowania klasy dziedzinowej na reprezentację relacyjną jest możliwa w statycznej zmiennej mapping, która przyjmuje na wejściu domknięcie. W niej definiujemy nazwę tabeli przez metodę table z pojedynczym argumentem określającym nazwę tabeli i poszczególnych kolumn z metodą, której nazwa odpowiada nazwie odwzorowywanemu atrybutowi klasy dziedzinowej, która akceptuje mapę, a w której z kolei parametr column wskazuje na nową nazwę kolumny (uff, można nabrać powietrza), np. (Listing 17-3):
 class Album {
String title
...
static mapping = {
table "RECORDS"
title column: "R_TITLE"
}
}
Jeśli nazwa klasy dziedzinowej lub atrybutu tworzy nazwę tabeli lub kolumny, która jest niedozwolona w SQL (jest słowem kluczowym SQL), wtedy odwzorowujemy ją za pomocą lewego (wstecznego) apostrofu, np.
 table "`order`"
Zmiana nazwy kolumny łączącej w odwzorowaniu jeden-do-jednego lub wiele-do-jednego jest identyczna do typowej zmiany nazwy kolumny, np. (Listing 17-4):
 class Album {
static belongsTo = [artist:Artist]
...
static mapping = {
// domyślnie ARTIST_ID
artist column: "R_CREATOR_ID"
}
}
Konfiguracja odwzorowania jeden-do-wielu wymaga najpierw decyzji, czy ma być jednokierunkowa (ang. unidirectional) czy dwukierunkowa (zwrotna, ang. bidirectional). Za pomocą joinTable zmieniamy nazwę i kolumny tabeli złączeniowej, np. (Listing 17-5):
 class Artist {
static hasMany = [albums:Album]
...
static mapping = {
albums joinTable:[name:'Artist_To_Records', key: 'Artist_Id', column: 'Record_Id']
}
}
Klucz key wskazuje na stronę wiodącą (jeden), a column na stronę docelową (wielu).

W przypadku dwukierunkowego odwzorowania jeden-do-wielu tablica złączeniowa nie jest wykorzystywana i opiera się na kluczu obcym.

Odwzorowanie wiele-do-wielu jest realizowane podobnie jak jednokierunkowe odwzorowanie jeden-do-wielu za pomocą tablicy złączeniowej - zmiana przez joinTable.

Hibernate wie jak odzworowywać wiele podstawowych typów javowych, np. java.lang.String staje się java.sql.Types.VARCHAR w SQL. Za pomocą parametru type można zmienić typ kolumny, np. (Listing 17-8):
 class Album {
...
String title
static mapping = {
title type: "text"
}
}
Typy Hibernate są wskazywane po nazwie i "text" odpowiada java.sql.Types.CLOB - patrz org.hibernate.Hibernate.

W książce autorzy przedstawiają odwzorowanie własnych typów w Grails ORM DSL na przykładzie pakietu JodaTime ze specjalizowanym typem org.joda.time.Duration (nie sądziłem, że jest on tak wyrafinowany, ale po zachwytach autorów nie mam złudzeń - "As an example, say you wanted to use the excellent JodaTime Java date and time API" - strona 525).

Zmiana konfiguracji odwzorowania typu kolumny może być dodatkowo określona przez parametr sqlType, np. (Listing 17-10):
 class Song {
...
org.joda.time.Duration duration
static mapping = {
duration type: org.joda.time.contrib.hibernate.PersistentDuration,
sqlType: "VARCHAR(120)"
}
}
Następnie pojawia się wystarczająco zaawansowany przykład stworzenia własnego typu w Hibernate i jego odwzorowanie w GORM DSL - MonetaryAmount. Wystarczająco zaawansowany, abym stwierdizł, że zdecydowanie za daleko zdryfowaliśmy od tematu głównego jakim jest Grails. Tutaj, fanatycy Hibernate znajdą coś dla siebie, czego mogliby nie znaleźć w innych książkach dedykowanych Hibernate. Interesujący sposób poznawania tajników Hibernate przez pryzmat Grails. Takie dwa w jednym :)

Domyślną strategią tworzenia identyfikatorów dla klas dziedzinowych w GORM jest poleganie na mechaniźmie właściwym dla bazy danych, np. dla MySQL GORM odpyta bazę danych ze wskazanej tabeli i jej kolumny id. Istnieje kilka mechanizmów tworzenia identyfikatorów w Hibernate, a tym samym i GORM DSL - increment, identity, sequence, hilo, seqhilo, uuid, guid, native, assigned, select, foreign oraz sequence-identity. Przypisanie wybranego to skorzystanie z parametru generator, np. (Listing 17-15):
 class Album {
...
static mapping = {
id generator: 'hilo', params:[table:'hi_value', column:'next_value', max_lo:100]
}
}
Znawcy Hibernate od razu zauważą podobieństwo mapy przekazywanej do parametru params z konfiguracją przez element param w hibernate'owym XMLu.

Możliwa jest konfiguracja identyfikatora złożonego składającego się z dwu lub więcej atrybutów odwzorowywanej klasy dziedzinowej przez parametr composite, np. (Listing 17-18):
 class Album implements Serializable {
String title
Artist artist
...
static mapping = {
id composite:["title", "artist"]
}
}
Identyfikator złożony wymaga, aby klasa realizowała interfejs java.io.Serializable. Odczyt egzemplarzy klasy dziedzinowej z identyfikatorem złożonym wymaga oczywiście podania wartości obu atrybutów, np.
 def a = Artist.findByName("Tool")
def album = Album.get(new Album(artist:a, title:"Lateralus"))
Grails ORM DSL znacznie upraszcza konfigurację odwzorowania klas dziedzinowych przez Hibernate, ale (strona 532) "this integration doesn't preclude you from using one of hibernate's other mapping strategies" (nie mogłem odmówić sobie przyjemności zacytowania zdania z "preclude" - tak rzadko się go spotyka :)).

Możliwe jest równoczesne wykorzystanie GORM DSL i typowej konfiguracji Hibernate do konfiguracji bazodanowej. Jeden nie wyklucza drugiego. Miejscem pliku hibernate.cfg.xml jest katalog grails-app/conf/hibernate. Kolejne 3 strony książki autorzy poświęcają przedstawieniu konfiguracji XMLowej w Hibernate dla hipotetycznej aplikacji GTunes, która tworzona jest przez całą książkę. Kolejny raz, gdzie miłośnicy Hibernate znajdą coś znajomego i (potencjalnie) ciekawego.

Kolejne strony rozdziału to przedstawienie tematu JPA w Grails i uruchomienia encji JPA, aczkolwiek (strona 535) "Grails doesn't support JPA directly (this support is still on the road map at the time of writing)". Wskazanie konfiguracji Grails opartej na adnotacjach wymaga konfiguracji w katalogu grails-app/conf (Listing 17-28):
 import org.codehaus.groovy.orm.hibernate.cfg.*
class DevelopmentDataSource {
def configClass = GrailsAnnotationConfiguration
...
}
Autorzy wskazują na ciekawy aspekt integracji JPA z Grails - encje JPA pisane są bezpośrednio w Javie, a mimo to "all of the dynamic finder and persistence methods work as if by magic" (strona 541). Za pomocą Groovy Meta Object Protocol (MOP), Grails rozszerza klasy o własne uproszczenia. W ten sposób możemy wykorzystać encje i konfigurację odzworowywań z istniejącej aplikacji bez konieczności jakichkolwiek zmian (!)

Na koniec autorzy przedstawiają temat mechanizmu kontroli poprawności atrybutów klas dziedzinowych przez statyczną zmienną constraints. Temat był przedstawiony w rozdziale 3. "Understanding Domain Classes" z moją relacją w Relacja z rozdziału 3. w "The Definitive Guide to Grails, Second Edition". I teraz najlepsze - związane ich z klasami javowymi (POJO) to stworzenie skryptu Groovy w tym samym pakiecie i katalogu (w ramach src/java), jak klasa, której dotyczy (!) Nazwa skryptu to złożenie nazwa klasy POJO zakończonego "Constraints", czyli dla pl.jaceklaskowski.encja.Pracownik byłby to skrypt o nazwie PracownikConstraints.groovy w katalogu src/java/pl/jaceklaskowski/encja. W ramach skryptu definiujemy wyłącznie pakiet i zmienną constraints, np. (Listing 17-30):
 package com.g2one.gtunes
constraints = {
number blank:false, maxSize:200
street blank:false, maxSize:250
...
}
Interesujące, nieprawdaż? Możemy pisać lub wykorzystać istniejące klasy dziedzinowe pisane w Javie bez żadnych zmian, a wciąż korzystać z zaawansowanych funkcjonalności Grails jak dynamiczne metody wyszukiwania, metody kryteriowe i rusztowanie. Właśnie mechanizm rusztowania jest niezwykle interesujący dla nieinwazyjnego wprowadzenia Grails do istniejących rozwiązań, jako chociażby interfejs administracyjny dla bazy danych pod kontrolą aplikacji pisanej bez udziału Grails. Cytując (strona 543): "The reality is that there are many cases where static typing is the better choice and conversely, there are many where dynamic typing is favorable". Nic dodać, nic ująć, tylko się zgodzić. Dla mnie bajka.

Tym samym doszliśmy do końca książki "The Definitive Guide to Grails, Second Edition", która jakkolwiek zawiera jeszcze dodatek opisujący język Groovy - Appendix "The Groovy Language", ale zapoznanie się z nim pozostawię zainteresowanym. Sam przechodzę do kolejnej książki "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama z The Pragmatic Programmers, więc o Groovy będę miał dedykowane 284 stron.