Grailsujemy z bazą, cz. II

Nasze ostatnie spotkanie z Grails zakończyliśmy na wygenerowaniu za pomocą scaffoldingu gotowej funkcjonalności CRUD. Pobawiliśmy się trochę, ale…

… ale dalej nasza aplikacja nie ma za wiele funkcjonalności. Dane nie zapisują się w bazie danych, ich odczyt też nie bardzo bo i nie ma czego odczytywać. Tym razem postanowimy coś z tym zrobić. Postaramy się zaimplementować choć część funkcjonalności CRUD w oparciu o Hibernate. Do dzieła.

Scaffolding jest bardzo fajny, jednakże został on stworzony w jednym celu – by ułatwić tworzenie klasy domenowej (lub jak mówi Jacek Laskowski – dziedzinowej). To właśnie pisanie testów dla niej, sprawdzenie poprawności odwzorowań i tak dalej jest celem stosowania rusztowania w aplikacji Grails. Kiedy już mamy pewność, że klasa dziedzinowa jest w porządku, wówczas możemy zabrać się za implementację reszty. Przyjrzyjmy się jeszcze raz naszej klasie dziedzinowej:

class Linki {String adress;
String description;
Date addDate;
Integer category_id;

static constraints = {
adress(blank:false, maxSize: 256, nullable: false)
addDate()
description(blank:false, maxSize: 1024, nullable:false)
category_id(blank:false, nullable:false)
}

}

Tak zdefiniowana klasa daje dość duże pojęcie o tym jak powinna wyglądać tabela w bazie danych. Sama baza jeszcze nie istnieje fizycznie w jakimkolwiek RDBMie, ale to za chwilę się zmieni. By zaś pozostać w zgodzie ze wszystkimi prawidłami sztuki – oto jak będą wyglądały tabele dla linków:

htj_linki_baza1

Mamy zatem podstawową tabelę z linkami i pomocnicza, w której zapiszemy kategorię (dokładnie jedną) danego odnośnika. Pora zaprząc do pracy Hibernate. Zanim jednakże to zrobimy wypada powiedzieć parę słów o samej bazie danych. Otóż standardowo będzie to MySQL, wybrany dlatego, że akurat mam go zainstalowanego na dysku 🙂 O swoich przygodach z driverem do JDBC pisałem wcześniej, zatem problemu być nie powinno. Jeżeli zaś chodzi o samego Hibernate, to nie zamierzam tutaj dawać jakiś rozbudowanych teoretyczych wykładów. Kod niech mówi za siebie. Do nauki używam bardzo fajnej książki, którą polecam każdemu.

Zanim zaczniemy pracę trzeba ściągnąć Hibernate i sterownik MySQLa dla JDBC (connector). Konkretne pliki JAR należy wypakować do katalogu /lib naszej aplikacji i jesteśmy gotowi do dalszej pracy (w przypadku Hibernate potrzebuje on też kilku innych bibliotek do pracy – zachęcam do sięgnięcia po książkę lub do dokumentacji). Plik /conf/DataSource.groovy wyglądał u mnie tak:

dataSource {
pooled = true
driverClassName = "com.mysql.jdbc.Driver"
username = "root"
password = ""
}
hibernate {
cache.use_second_level_cache=true
cache.use_query_cache=true
cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
}
// environment specific settings
environments {
development {
dataSource {
dbCreate = "update" // one of 'create', 'create-drop','update'
url = "jdbc:mysql://localhost/chlebik"
}
}

Próbny rozruch (czy odnajduje klasy JDBC) i… zonk. ClassNotFoundException. Okazało się, że konwencja ponad konfigurację nie dała rady. I teraz zagwozdka – jak cholercia dodać do CLASSPATH Grailsów zewnętrzny plik JAR? Chwil kilka z guglem i cóż się okazuje – wymaga to małego hacka, który dobił mnie totalnie. W katalogu /conf należy utworzyć plik PreInit.groovy i tam wpisać taki oto twór (Update: W bugtrackerze frameworka znalazłem informację, że plik PreInit.groovy zmienia nazwę na BuildSettings.groovy, ale zmiana ta wywala u mnie błąd zatem zostaję przy starej nazwie):

grails.compiler.dependencies = { fileset(dir:'D:/Java/Howtojava/lib/mysql-connector-java-3.1.14/',includes:'*.jar');
fileset(dir:'D:/Java/Howtojava/lib/hibernate-distribution-3.3.1.GA/',includes:'*.jar')}

Tak to wygląda u mnie, ścieżki są bezwzględne (inaczej się jakoś nie dało). Domyślam się, że cała ta zabawa z CLASSPATH i tak dalej wiąże się mocno z wdrażaniem aplikacji, ale póki co nie za bardzo mi się chce zabawiać z meandrami Anta czy innego Mavena. Póki co takie rozwiązanie działa i do prac developerskich w zupełności wystarcza.

Oczywiście skoro mamy już zaimportowane biblioteki trzeba skonfigurować samego Hibernate. Tworzymy plik /conf/hibernate/hibernate.xfg.xml o takiej oto treści:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/chlebik</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password"></property>
<property name="hibernate.connection.pool_size">10</property>
<property name="show_sql">true</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- Mapping files -->
<mapping resource="grails-app/domain/Linki.hbm.xml"/>
</session-factory>
</hibernate-configuration>

Dane jak widać są zdublowane (występują w konfiguracji DataSource jak i dla Hibernate). Na razie nie wiem co z tych plików ma pierwszeństwo zatem zostawiam dane i tu i tu. Zgodnie z poleceniem z książki teraz wypadałoby stworzyć plik odwzorowań dla klasy Linki. Chwilowo dla jasności przykładu i łatwości zakomentowałem właściwość category_id oraz add_date. Oto kod:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="Linki" table="htj_links">

<id name="id" type="int" column="link_id">
<generator class="native" />
</id>

<property name="address" column="address" type="string" />
<property name="description" column="description" type="string" />

</class>

</hibernate-mapping>

Na szybko utworzona tabelka w bazie:

CREATE TABLE `htj_links` (
`link_id` int(11) NOT NULL auto_increment,
`address` varchar(255) character set utf8 collate utf8_polish_ci NOT NULL,
`description` varchar(255) character set utf8 collate utf8_polish_ci NOT NULL,
PRIMARY KEY (`link_id`)
) ENGINE=InnoDB;

I wrzucony jeden mały link. Do tego oczywiście naszą klasę dziedzinową trzeba przerobić na odpowiednią modłę, aby mogła występować wraz z Hibernate:

public class Linki {

String address;
String description;

static constraints = {
id(blank:false, nullable: false)
address(blank:false, maxSize: 255, nullable: false)
description(blank:false, maxSize: 1024, nullable:false)

}
Linki() {

}

public Linki( Integer id, String address, String description)
{
this.id = id;
this.address = address;
this.description = description;
}

public Integer getId()
{
return id;
}

public void setId( Integer link_id )
{
this.id = link_id;
}

public String getAddress()
{
return address;
}

public void setAddress( String address )
{
this.address = address;
}

public String getDescription()
{
return description;
}

public void setDescription( String description )
{
this.description = description;
}

}

Ufff, tyle roboty, a efektów wciąż nic. Teraz dopiszemy sobie taki oto kod do naszego kontrolera – LinkiController.groovy:

private static final SessionFactory sessionFactory;

static {
try {

sessionFactory = new Configuration ().configure ().buildSessionFactory ();
System.out.println("Identyfikator utworzonego obiektu: " + sessionFactory);
}
catch (Throwable ex) {
System.err.println("Inicjacja SessionFactory nie powiodła się." + ex);
throw new ExceptionInInitializerError (ex);
}
}

public static SessionFactory getSessionFactory () {
return sessionFactory;
}

No i teraz pozostaje tylko uruchomić naszą aplikację. Kiedy w standardowym wyjściu (konsoli) pojawi nam się coś na kształt:

Identyfikator utworzonego obiektu: org.hibernate.impl.SessionFactoryImpl@79404a

To znaczy, że odnieśliśmy sukces. Jak napisałem wcześniej – do istniejącej tabeli z linkami dodałem testowy rekord (wszystkie operacje na bazie dokonywane były przez PHPMyAdmin – nie jest to może podstawowe wyposażenie javowca, no ale ja ułatiwiam sobie życie i póki co pracuję z narzędziami, które wykorzystuję na co dzień). Skoro mamy rekord w bazie danych to wypadałoby na pewno go wyciągnąć. W tym samym kontrolerze pod linijką drukującą informację o naszym obiekcie należy umieścić taki oto kod:

Session session = sessionFactory.openSession();
List linki = session.createQuery("from Linki").list();
System.out.println("Pobrałem rekordów: " + linki.size());

Linki pojedynczyLink = linki.get(0);
System.out.println("Identyfikator rekordu to: " + pojedynczyLink.getId() );
System.out.println("Przypisany link to: " + pojedynczyLink.getAddress() );
System.out.println("Opis rekordu to: " + pojedynczyLink.getDescription() );

Zwrócić należy uwagę, że w naszym przypadku w klauzuli FROM mamy nazwę klasy, a nie tabeli! To dość istotne, inaczej otrzymalibyśmy komunikat mówiący o niemożności znalezienia tabeli. Jako, że rekord będziemy mieli raptem jeden, dostajemy się do niego bezopśrednio poprzez odwołanie się do pierwszego elementu zwróconej listy (oczywiście to tylko przykład, w normalnych aplikacjach takich rzeczy się nie robi). Ja w wyniku otrzymałem ostatecznie taki oto efekt (pogrubienia dodałem dla czytelności):

Identyfikator utworzonego obiektu: org.hibernate.impl.SessionFactoryImpl@1af7e68
Hibernate: select linki0_.link_id as link1_0_, linki0_.address as address0_, linki0_.description as descript3_0_ from htj_links linki0_

Pobrałem rekordów: 1
Identyfikator rekordu to: 1
Przypisany link to: http://www.chlebik.wordpress.com
Opis rekordu to: strona człowieka, który do końca nie wie o co chodzi w tym wszystkim.

I teraz by nie przedłużać (jest trzecia nad ranem) w tymże samym kontrolerze dopiszmy do naszego kodu takie oto coś:

pojedynczyLink.setDescription(" choć może autor i na czymś się zna....");
session.beginTransaction();
session.save(pojedynczyLink);
session.getTransaction().commit();
session.close();

Po uruchomieniu tego kodu zobaczmy efekt, po czym zrestartujmy aplikację. Co otrzymaliśmy z bazy? Może coś takiego:

Identyfikator rekordu to: 1
Przypisany link to: http://www.chlebik.wordpress.com
Opis rekordu to: choć może autor i na czymś się zna....

Cudnie. Jak widać początkowy wysiłek opłacił się i oto w kilku zaledwie linijkach możemy pobrać rekordy, zmienić jeden z nich (lub wszystkie), po czym zapisać je do bazy. Mam nadzieję, że przedstawiony powyżej kod daje choćby mgliste pojęcie o sile Hibernate, czy tez ORMów w ogólności. Następne boje z bazą (zrezygnujemy ze scaffoldingu – tak przy okazji można uruchomić sobie aplikację, przejść do sekcji z linkami i spróbować się pobawić) już niedługo, mam nadzieję, że nie stracicie cierpliwości.

Advertisements

6 thoughts on “Grailsujemy z bazą, cz. II

  1. xis

    Cześć,
    Na wstępie – bardzo fajny blog, wylądował w moich RSS 🙂

    Nie bardzo rozumiem po co dodajesz do swojej aplikacji Hibernate, przeciez Grails posiada własny ORM (zwany GORM), który to bazuje na.. Hibernate właśnie 🙂 Więc masz już Hibernate.
    Ponadto wstawienie sterownika JDBC do katalogu lib/ powinno w zupełności wystarczyć (mi wystarcza, bawię się na MS SQL Server i sterowniku JTDS).
    Ostatnia uwaga: jeśli będziesz używał “domyślnego” Hibernate z Grails to naprawdę wystarczy edycja pliku DataSources.groovy.
    No, chyba, że kombinujesz coś bardziej skomplikowanego, a ja to przeoczyłem 🙂
    Pozdrawiam

  2. chlebik Post author

    Witam,

    dodaję Hibernate (lub też inaczej, chcę na nim operować bezpośrednio), gdyż nigdy jeszcze tego nie robiłem. A zasadniczo standardem w świecie javy jest uzywanie Hibernate stąd moje nim zainteresowanie. W następnym wpisie wezmę się za porządki konfiguracyjne i poprzenoszenie tego kodu w odpowiednie miejsca, dziś nad ranem po prostu nie miałem już siły 🙂

    Zaś co do ścieżek i tak dalej – domyślam się, że winę za to ponosi brak mojej znajomości narzędzi do budowania projektów – choćby Anta czy Mavena. Dlatego też pewnikiem problemy ze ścieżkami w projekcie. Jednakże na razie poznaję sam kod Groovy i same Grails, na deployment przyjdzie czas zaniedługo.

  3. xis

    No, jeśli masz takie właśnie założenia to OK. W Grails Hibernate już jest i jest już skonfigurowany (nie trzeba nawet robić mapowania) – jako GORM.
    Mi też było trudno uwierzyć, że to jest TAK proste!
    (odnośnie Anta – Grailsy używają “swojego” Ganta – to jakaś mania dodawania “G” przed nazwą 😉 )

    Jeśli mogę Ci doradzić (choć ekspertem raczej nie jestem) – do zabawy i poznawania Hibernate jako takiego lepiej nadałaby się jakaś natywna aplikacja Javowa. Grails tu Ci troszkę miesza szyki (vide Twoje problemy z classpath i podwójne pliki konfiguracyjne), bo dostarcza gotowe rozwiązanie. Zbyt gotowe – jak się okazuje 😉

    Powodzenia!

  4. Marek Podsiadły

    Korzystanie bezpośrednio z Hibernate w Grailsach, to chyba lekka przesada! Po to wymyślono Grails, aby ułatwić sobie życie, a Ty je sobie komplikujesz. Jeśli chcesz rozpracować Grails, to lepiej nauczyć się korzystania z GORM. Tworzony kod wtedy będzie prostszy. Pobranie rekordu z bazy, jego modyfikacja i zapis z powrotem w Twoim wykonaniu zajmuje kilka linijek, korzystając z GORM zrobisz te same operacje w dwóch linijkach:

    def pojedynczyLink = Linki.get(1)
    pojedynczyLink?.description = " choć może autor i na czymś się zna...."

    Po wykonaniu tego kodu rekord w bazie się zmodyfikuje, bez jawnego wywoływania metody save(). Prawda, że łatwiej?

  5. chlebik Post author

    Ano widze, że szybko. No nic, chodziło mi o nabranie pewnej ogłady jeśli chodzi o ORMy w ogólności. Trzeba będzie pożeglować dalej w takim razie w kierunku GORMa i wykorzystać jego potencjał. Dziękuję za informację.

  6. mati

    Tak jak napisali xis i Marek, Hibernate’a lepiej się uczyć poza Grails. A przy Grails skupić się na GORM, który jest znacznym uproszczeniem. Wrzucenie sterowników do katalogu lib grailsów powinno wystarczyć.

    Domyślnie projekt wygenerowany w NetBeans korzysta do uruchamiania projektu z Jetty, a ten przy okazji odpala Hypersonica (bazę). Plik DataSource.groovy ma już skonfigurowane wszystko co potrzeba, żeby z tej bazy korzystać – połączenia, generowanie tabelek na podstawie encji, itp. To w zupełności wystarcza, żeby pobawić się z Grails i poznać to co faktycznie jest specyficzne dla grailsa.

    Co do transakcji, to Grails to też robi za Ciebie i nie musisz tego ręcznie obsługiwać – chyba, że potrzebujesz niezależnie to kontrolować, wtedy jak najbardziej możesz.

    Zwróć uwagę też, że grails automagicznie dostarcza identyfikatory i wersjonowanie, więc tego też nie musisz robić. A relacje robi się przez deklarowanie właściwości będącej referencją do innej klasy – nie musisz jawnie deklarować category_id w klasie Linki. Klucze obce grails też zrobi za Ciebie.

    Mimo tego, że można było to zrobić prościej, to post jest super, bo pokazuje, że nie jesteśmy zdani tylko na to, co grails daje z paczki, ale możemy mieć nad tym całkiem sporą kontrolę. Np. możemy zastosować zupełnie inny framework ORM, a z grails wykorzystać tylko dobrodziejstwa webowe.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s