Grails ORM część 4 – zapytania
GORM pozwala nam na wykonywanie zapytań w kilka różnych sposobów. Możemy do nich zaliczyć podstawowe metody operujące na klasach domenowych, dynamic finders (metody typu find*), kryteria (które pozwalają budować zapytania podobnie jak robi to JaQu) albo wykorzystać stary (dobry) HQL.
Metody klas domenowych
Kawałek kodu chyba najlepiej pokaże, jakie metody możemy wywołać:
// Wylistuj wszystkie osoby def persons = Person.list() // Pobierz osobę o id=3 def person = Person.get(3) // Pobierz osoby o id 3,4,5 def persons = Person.getAll(3, 4, 5) // Pobierz 10 osób z offsetem 100, sortując po nazwisku malejąco def persons = Person.list(max:10, offset:100, sort:"lastname", order:"desc")
Dla metody list() możemy dodatkowo określić ilość zwróconych rekordów, od którego rekordu rozpocząć, sortowanie łączenie z kierunkiem, a także w jaki sposób pobrać asocjacje (eager/lazy). Metoda get() po prostu pobiera rekord o zadanym id, a getAll() rekordy o zadanych id. Jeśli któryś z nich nie będzie istniał, lista wyników będzie zawierała null.
Dynamic finders
Dynamic finders to metody tworzone dynamicznie w środowisku uruchomieniowym na podstawie właściwości klasy. Że jak? Że tak:
class Person { String firstname String lastname Date dateOfDeath } def p1 = Person.findByFirstname("Mateusz") def p2 = Person.findByLastname("Mrozewski") def p3 = Person.findByFirstnameAndLastnameLike("Mateusz", "M%") def p4 = Person.findByDateOfBirthIsNull()
Wypisane metody to tylko kilka możliwych przykładów. Możemy takie metody wywoływać dla każdej właściwości. Tak jak w przykładzie wyszukania osoby p3 możemy połączyć warunki dla dwóch dowolnych właściwości z operatorem AND lub OR (maksimum dwóch - dla większej ilości powinniśmy wykorzystać kryteria lub HQL). Dwa typy metod to findBy*, który zwraca pierwszy rekord wyniku oraz findAllBy*, który zwraca wszystkie pasujące wyniki. A dozwolone operacje to:
- InList - czy wartość znajduje się na podanej liście
- LessThan - czy wartość jest mniejsza niż podana
- LessThanEquals - czy wartość jest mniejsza lub równa niż podana
- GreaterThan - czy wartość jest większa niż podana
- GreaterThanEquals - czy wartość jest większa lub równa niż podana
- Like - podobne do SQLowego LIKE
- ILike - jak wyżej, tylko case insensitive (to nie produkt apple'a
) - NotEqual - nie równe (nie ma samego equal, bo to równoważne z np. findByName)
- Between - czy wartość jest pomiędzy zadanymi
- IsNotNull - czy wartość nie jest null'em
- IsNull - czy wartość jest null'em
Ten typ zapytań możemy również wykorzystać dla asocjacji. Do tych zapytań możemy też dodać parametry takie jak w metodzie list(). Prawda, że fajne? Osobiście uważam, że przy zastosowaniu już dwóch parametrów przestaje być to czytelne i dużo łatwiej pisze się i analizuje kryteria, ale do prostych zapytań jest to jak najbardziej ciekawe rozwiązanie.
Ale chwileczkę, skąd te wszystkie metody się wzięły? Przecież nic nie dziedziczyliśmy, nic nie implementowaliśmy. Tutaj z pomocą przychodzi nam dynamiczność Groovy oraz to, jak zostało to wykorzystane w Grails. Konkretnie chodzi tu o wykorzystanie metaklas i ich właściwości methodMissing. W książce The Definitive Guide to Grails w dodatku poświęconym Groovy możemy znaleźć nawet prosty przykład implementacji dynamic finder.
Kryteria
Kryteria to sposób na budowanie złożonych zapytań poprzez wykorzystanie składni Groovy. A konkretnie wykorzystana jest tu koncepcja builders.
Nas interesuje jednak jak to wygląda w kodzie. Oto mały przykład z dokumentacji:
def c = Account.createCriteria() def results = c { like("holderFirstName", "Fred%") and { between("balance", 500, 1000) eq("branch", "London") } maxResults(10) order("holderLastName", "desc") }
W tym przykładzie zostały utworzone kryteria dla klasy domenowej Account. Następnie rozbudowaliśmy je o kolejne warunki - ten prosty przykład chyba dość dobrze pokazuje jakie są możliwości kryteriów.
Co można w takich kryteriach zrobić:
- wykorzystać wszystkie metody z klasy Hibernate Restrictions
- wykorzystać operatory logiczne AND, OR i NOT
- pytać o inne klasy domenowe będące w asocjacji z główną klasą
- wykorzystać wszystkie metody z klasy Hibernate Projections
- wykorzystać wszystkie metody z klasy Hibernate ScrollableResults
- określić ilość pobranych rekordów oraz od którego rekordu zacząć
- określić czy asocjacje będą pobierane w trybie EAGER czy LAZY
Nie wypisuję wszystkich możliwości, gdyż jest tego mnóstwo, a wszystko jest w Javadoc'ach pod podanymi odnośnikami. Jednak ta krótka przynajmniej pokazuje jak szerokie mamy możliwości. GORM to nie tylko CRUD
HQL
W GORM możemy też wykorzystać HQLa (Hibernate Query Language). Jest to język zapytań bardzo podobny do SQLa, ale nie operujemy w nim na tabelkach, tylko na encjach (klasach domenowych). Mamy do dyspozycji trzy metody, które możemy wywołać na naszej klasie domenowej:
- find - zwraca pierwszy wynik pasujący do zapytania
- findAll - zwraca wszystkie wyniki pasujące do zapytania
- executeQuery - wykonuje zapytanie, niekoniecznie zwracając wyniki
Kilka przykładów:
// Znajdź pierwszą osobę o imieniu Mateusz def person = Person.find("from Person as p where p.firstname = ?", ['Mateusz']) // Znajdź wszystkie osoby o imieniu Mateusz i nazwisku na M def results = Person.findAll("from Person as p where p.firstname = :firstname and p.lastname like :lastname", [firstname:'Mateusz', lastname:'M%'])
Niezbędnikiem jest zapoznanie się z rozdziałem dokumentacji Hibernate poświęconej HQL.
I to tyle jeśli chodzi o wykonywanie zapytań w GORM. Do omówienia zostały jeszcze tematy "zaawansowane", takie jak transakcje, zdarzenia, keszowanie i wpływanie na strukturę bazy danych. Temat na następnego posta.
Świetna notka, idealnie skompresowane wszystko, co do wyszukiwania danych w Grails potrzebne
Mi najbardziej podobają się kryteria. Sam miałem dopiero niedawno okazję używać tych z Hibernate i jestem pod ogromnym wrażeniem budowanych za ich pomocą zapytań. Dodatkowo domknięcia, które zaoferował Groovy sprawiają, że jest to rozwiązanie naprawdę estetyczne, a mimo to o ogromnych możliwościach.