Stronnice Chlebika – Java Blog for Newbies

marzec 6, 2009

Grailsujemy z bazą, cz. II

Zaszufladkowany do: Grails, Hibernate, HowToJava, Java — Tagi:, , , , — chlebik @ 2:19 am

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.

Blog na WordPress.com.