18 kwietnia 2009

Dokończenie rozdziału 10. o Grails ORM (GORM) z DGG2

Dokończenie relacji rozdziału 10. "GORM" z książki "The Definitive Guide to Grails, Second Edition".

Jednym z elementów Hibernate, który nie trafił do specyfikacji JPA (Java Persistence API) były zapytania kryteriowe (ang. criteria queries). Jest to sposób na tworzenie zapytań dynamicznie, który w Grails stał się jeszcze bardziej trywialny z pomocą wsparcia "budowniczych" (ang. builders) z Groovy. Mechanizm "budowniczych" polega na wykonaniu hierarchii metod i domknięć w celu stworzenia hierarchicznych struktur drzewiastych, jak np. dokumenty XMLowe czy hierarchia komponentów GUI.

GORM rozszerza każdą klasę dziedzinową o statyczną metodę createCriteria() do utworzenia egzemplarza zapytania kryteriowego. Następnie, korzystamy z 4 metod akceptujących domknięcie do zbudowania bardziej szczegółowego zapytania:
  • get() do odszukania pojedynczego egzemplarza klasy dziedzinowej
  • list() dla listy klas dziedzinowych spełniających warunek
  • scroll(), który zwraca ScrollableResults
  • count(), którego wynikiem jest liczba egzemplarzy
np. (Listing 10-11):
 def c = Album.createCriteria()
def results = c.list {
eq('genre', 'Alternative')
between('dateCreated', new Date()-30, new Date())
}
Metody w ramach domknięcia są zamieniane na odpowiednie wywołania metod na egzemplarzu klasy org.hibernate.criterion.Restrictions z Hibernate.

Domknięcie jest blokiem kodu, który może być przypisywany do zmiennej oraz może odwoływać się do zmiennych z otoczenia. W ten sposób można stworzyć wzorzec zapytania (domknięcie), przypisać do zmiennej i wykonać z nim jedną z 4 wymienionych metod, np. (Listing 10-12):
 def today = new Date()
def queryMap = [genre:'Alternative', dateCreated: [today-30, today]]
def query = {
queryMap.each { key, value ->
if (value instanceof List) {
between(key, *value)
} else {
like(key, value)
}
}
}
def criteria = Album.createCriteria()
println(criteria.count(query))
Interesującym elementem w tym przykładzie jest użycie operatora "spread" (gwiazdka przy value), który dzieli listę na dwa argumenty wejściowe (tablicę dwuargumentową) dla between, która wymaga 3 argumentów (więc niewprost zakłada się, że value jako lista będzie dwuelementowe). Reszta powinna być jasna, aczkolwiek dla mnie była niezwykle odświeżająca.

Wyszukiwanie egzemplarzy klas dziedzinowych po stanie związanych z nimi klas dziedzinowych, to wykonanie metod, których nazwy odpowiadają nazwom pól łączących klasy dziedzinowe w związki, np. (Listing 10-13):
 def criteria = Album.withCriteria {
songs {
ilike('title', '%Shake%')
}
}
Jakkolwiek użyliśmy nowej metody statycznej withCriteria() to dotyczy to również createCriteria().

GORM udostępnia metodę projections(), której parametrem wejściowym jest domknięcie, do tworzenia specjalizowanych zapytań z SQLowymi count, distrinct czy sum. Wewnątrz domknięcia wykonujemy metody org.hibernate.criterion.Projections z Hibernate, np. countDistinct().

GORM posiada mechanizm "zapytań przez przykład" (ang. query by example), gdzie metodzie find() czy findAll() przekazujemy egzemplarz poszukiwanej klasy z odpowiednim stanem. W połączeniu z tworzonym przez Groovy domyślnym konstruktorem akceptującym serię atrybut/wartość daje to interesujące konstrukcje, np. (Listing 10-15):
 def album = Album.find( new Album(title:'Odelay') )
Zapytanie jest budowane na podstawie wartości przekazywanych w konstruktorze.

Do pracy z zapytaniami HQL w Grails mamy metody find(), findAll() oraz executeQuery(). Podajemy im parametr tekstowy będący zapytaniem HQL. Zapytania z "?" wypełniane są wartościami z listy będącą ich drugim parametrem wejściowym (podobnie z parametrami nazwanymi), np. (Listing 10-18):
 def album = Album.find(
'from Album as a where a.title = :theTitle',
[theTitle:'Odelay'])
Stronicowanie w GORM to wykonanie zapytania HQL z listą z parametrami max i offset, np.
 Album.findAllByGenre("Alternative", [max:10, offset:20])
Konfiguracja GORM jest w pliku grails-app/conf/DataSource.groovy w sekcji dataSource. Większość parametrów konfiguracyjnych Hibernate jest dostępna w GORM, np. wyświetlanie wysyłanych zapytań to
 dataSource {
logSql = true
}
albo ustawienie dialektu
 dataSource {
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
}
Pełne nazwy parametrów konfiguracyjnych Hibernate określamy w sekcji hibernate we wspomnianym grails-app/conf/DataSource.groovy, np.
 hibernate {
hibernate.connection.isolation=4
}
Zapisanie zmian w bazie danych jest domyślnie buforowane do późniejszego, bardziej odpowiedniego momentu niż każdorazowe zapisanie w bazie bezpośrednio po zmianie. Jeśli nie interesuje nas takie zachowanie GORM, możemy wykonać metody save() oraz delete() z parametrem flush ustawionym na true, np. (Listing 10-24):
 def album = new Album(...)
album.save(flush:true)
Oczywiście wypchnięcie zmian pojedynczego egzemplarza klasy dziedzinowej powoduje wypchnięcie zmian w całej sesji Hibernate (wykonanie metody Session.flush()).

Dostęp do aktualnie otwartej sesji Hibernate jest możliwy dzięki mechanizmowi wstrzeliwania zależności ze zmienną sessionFactory, np. (Listing 10-26):
 def sessionFactory

def index = {
def session = sessionFactory.currentSession()
}
Istnieje również inny sposób ze statyczną metodą withSession() dostępną w każdej klasie dziedzinowej, której domknięcie przyjmuje sesję Hibernate jako parametr wejściowy, np. (Listing 10-27):
 def index = {
Album.withSession { session ->
...
}
}
W rozdziale pojawia się wiele innych informacji nt. integracji GORM z Hibernate, jednakże są one związane z samym działaniem Hibernate i ich rozpoznanie zostawiam zainteresowanym jako dodatkową zachętę do lektury książki DGG2. Przed nami relacja z rozdziału 11. "Services" o klasach usługowych.