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 {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.
String title
...
static mapping = {
table "RECORDS"
title column: "R_TITLE"
}
}
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 {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):
static belongsTo = [artist:Artist]
...
static mapping = {
// domyślnie ARTIST_ID
artist column: "R_CREATOR_ID"
}
}
class Artist {Klucz key wskazuje na stronę wiodącą (jeden), a column na stronę docelową (wielu).
static hasMany = [albums:Album]
...
static mapping = {
albums joinTable:[name:'Artist_To_Records', key: 'Artist_Id', column: 'Record_Id']
}
}
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 {Typy Hibernate są wskazywane po nazwie i "text" odpowiada java.sql.Types.CLOB - patrz org.hibernate.Hibernate.
...
String title
static mapping = {
title type: "text"
}
}
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 {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 :)
...
org.joda.time.Duration duration
static mapping = {
duration type: org.joda.time.contrib.hibernate.PersistentDuration,
sqlType: "VARCHAR(120)"
}
}
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 {Znawcy Hibernate od razu zauważą podobieństwo mapy przekazywanej do parametru params z konfiguracją przez element param w hibernate'owym XMLu.
...
static mapping = {
id generator: 'hilo', params:[table:'hi_value', column:'next_value', max_lo:100]
}
}
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 {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.
String title
Artist artist
...
static mapping = {
id composite:["title", "artist"]
}
}
def a = Artist.findByName("Tool")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 :)).
def album = Album.get(new Album(artist:a, title:"Lateralus"))
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.*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 (!)
class DevelopmentDataSource {
def configClass = GrailsAnnotationConfiguration
...
}
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.gtunesInteresują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.
constraints = {
number blank:false, maxSize:200
street blank:false, maxSize:250
...
}
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.
Nie miałem pojęcia o istnieniu takich rzeczy jak hilo i seqhilo ... Człowiek uczy się całe życie.
OdpowiedzUsuńPozdrowienia dla brata Leszka (chyba dobrze pamiętam imie?)
Właśnie to jest piękne w czytaniu wartościowych książek - uczysz się nie tylko tematu głównego książki, ale również wiele pobocznych (w tym przypadku był to Hibernate, ale przy wcześniejszej książce o Grails miałem możliwość poznania wiele rozwiązań dostępnych w Grails jako wtyczki).
OdpowiedzUsuń"Dla brata Leszka"?! Tego nie chwyciłem :( Brata Ryśka, albo Edzia to rozumiem, ale Leszek odpada. Musisz mi to wyjaśnić na priv.