Dziś przyszedł czas na coś większego. Mam nadzieję, że ilość kodu nie będzie odstraszająca. Ponieważ dziś zaimplementujemy coś z pomocą tandemu Spring + Hibernate. Konkretnie przebudujemy troszeczkę widok strony głównej po to, aby móc wyświetlić ostatnie informacje o aplikacji (podobną rzecz robiliśmy z ProgramBash). Jest to o tyle ciekawe, iż użyjemy Springa w całej jego krasie, stworzymy pierwszą tabelę w naszej bazie, nauczymy się czegoś więcej o kontrolerach, używaniu Hibernate poprzez klasy Springa, a ostatecznie troszeczkę podlejemy to sosem od FreeMarkera. Do dzieła.
Zaczniemy od bazy. Zainstalowałem ją jakiś czas temu, a do naszej aplikacji podpiąłem chwilkę później. W ten sposób wyposażeni możemy stworzyć pierwszą tabelę w bazie danych – konkretnie news:
CREATE TABLE news ( id serial PRIMARY KEY, title varchar(127) NOT NULL, content text NOT NULL, data date NOT NULL ) WITHOUT OIDS ;
Co zaowocuje takim komunikatem:
NOTICE: CREATE TABLE will create implicit sequence "news_id_seq" for serial column "news.id"
NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "news_pkey" for table "news"
Zapytanie zostało wykonane w 454 ms i nie zwróciło żadnych wyników.
Mówiąc krótko – została utworzona sekwencja, która została przypisana do kolumny z naszym kluczem głównym (ten też został zresztą utworzony jak widać). Mamy zatem tabelę, na której będziemy pracować – dodałem do niej też 2 rekordy, aby mieć jakieś dane do pracy. Zajmiemy się teraz samym Springiem. Na czym bowiem polega jego zajefajność? Otóż podstawą jest właśnie wstrzykiwanie zależności – mamy obiekt, którego składową jest inny obiekt. Konfigurujemy te 2 obiekty w pliku XML, a Spring zajmie się utworzeniem obiektu, jego przekazaniem i wszystkim po drodze. Jest to o tyle praktyczne, iż redukuje ilość kodu, który trzeba za każdym razem napisać. Deklarujemy po prostu, że potrzebujemy w danym obiekcie innego obiektu i viola – działa. Suchy opis jednakże niewiele znaczy, zatem lepiej przerobić rzecz w praktyce.
Zaczniemy od klasy domenowej dla naszego newsa. Dla tej i pozostałych klas domenowych utworzymy oddzielny pakiet com.wordpress.chlebik.domain. Kod wygląda tak:
package com.wordpress.chlebik.domain; import java.io.Serializable; import java.sql.Date; /** * Klasa domenowa dla newsa w serwisie * * @author chlebik */ public class News implements Serializable { private long id; private String title; private String content; private Date data; /** * Konstruktor * * @author chlebik */ public News() {} // Gettery public long getId() { return id; } public String getTitle() { return title; } public String getContent() { return content; } public Date getData() { return data; } // Settery public void setId( int id ) { this.id = id; } public void setTitle( String title ) { this.title = title; } public void setContent( String content ) { this.content = content; } public void setData( Date data ) { this.data = data; } }
Nihil novi sub sole. Klasa jest prosta i sztampowa do bólu. Mapowanie:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.wordpress.chlebik.domain.News" table="news"> <id column="id" name="id" type="int"> <generator/> </id> <!-- zwykle kolumny --> <property column="content" name="content" type="string"/> <property column="title" name="title" type="string"/> <property column="data" name="data" type="date"/> </class> </hibernate-mapping>
Należy pamiętać o dopisaniu mapowania do pliku konfiguracyjnego Hibernate. Teraz wypadałoby zaimplementować klasę dostępu do naszej klasy domenowej, czyli mówiąc krótko piszemy DAO. Rzecz jasna wychodzimy od interfejsu, który zdefiniuje kontrakt jaki chcemy by spełniały ew. klasy implementujące. Uwaga! Dla tych co nie wiedzą dlaczego używamy interfejsów ważne wskazanie – posiadając interfejs korzystamy przy ewentualnej pracy z polimorfizmu. Zatem nagłe przepisanie aplikacji z MySQLa na PostgreSQL i na odwrót nie stanowi problemu, gdyż ew. obiekty przekazujemy rzutując w górę na interfejs.
Rzecz jasna w przypadku newsów na stronie głównej problemu nie ma – będzie tam póki co jedna metoda, która będzie nam wyciągała nasze newsy w określonej ilości. Może i dla takiego zastosowania możnaby od razu sklepać klasę, ale ani to profesjonalne, ani nie wykształca dobrych zachowań. Zatem tworzymy prosty interfejs w pakiecie com.wordpress.chlebik.dao.interfaces.
package com.wordpress.chlebik.dao.interfaces; import com.wordpress.chlebik.domain.News; import java.util.List; /** * Interfejs specyfikujacy kontrakt dla klas DAO newsow w serwisie * * @author chlebik */ public interface NewsDaoInterface { public List<News> getNews( int counter ); }
Interfejs mamy – teraz przyszedł czas na klasę go implementującą. I tutaj pierwszy styk Springa z Hibernate. Ten pierwszy posiada gotowe klasy, które służą obsłudze zapytań SQLa poprzez Hibernate. Oczywiście skorzystamy z tej możliwości, tym bardziej, że ułatwia to pisanie kodu. Jak bowiem wyglądałaby nasza klasa DAO? Zaimplementowalibyśmy metodę, w której wyciągalibyśmy sesję, potem ją zamykali, obsługa wyjątków, blech, coś brzydkiego, prawie jak JDBC. Zaczniemy od konfiguracji – musimy poinformować Springa, że używać będziemy Hibernate. Jednakże wprowadzimy pewien porządek, coby nie mieszać niepotrzebnie w dotychczasowym pliku konfiguracyjnym. Stworzymy oddzielny plik XMLa z konfiguracją bazy danych, a następnie zaimportujemy go do obecnie już istniejącego frontcontroller-servlet.xml. Nasz plik nazwiemy database-config.xml i wrzucamy do katalogu WEB-INF:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="dataSource"> <property name="driverClassName"> <value>org.postgresql.Driver</value> </property> <property name="url"> <value>jdbc:postgresql://88.198.31.169/chlebik</value> </property> <property name="username"><value>MOJ_USER</value></property> <property name="password"><value>MOJE_HASLO</value></property> </bean> <bean id="mySessionFactory"> <property name="mappingResources"> <list> <value>News.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect"> org.hibernate.dialect.PostgreSQLDialect </prop> </props> </property> <property name="dataSource"> <ref bean="dataSource"/> </property> </bean> </beans>
Zaś do pliku frontcontroller-servlet.xml dopisujemy taki kod (dotyczy bazy danych jak i naszego beana z newsami):
<import resource="database-config.xml"/> <bean id="newsDao" class="com.wordpress.chlebik.dao.implementation.NewsDao"> <property name="sessionFactory" ref="mySessionFactory" /> </bean> <bean name="/index.html" class="com.wordpress.chlebik.controllers.IndexController"> <property name="newsDao" ref="newsDao" /> </bean>
Mamy pokonfigurowane stosowne beany by ogarnąć połączenie z bazą danych. W ten sposób wstępnie poinformowaliśmy Springa (zdefiniowaliśmy bean) o istnieniu fabryki sesji Hibernate. Ślicznie. Dzięki temu możemy teraz zastanowić się nad implementacją klasy DAO dla newsów. Mamy tutaj kilka możliwości:
- HibernateDaoSupport – jest to klasa, którą powinny rozszerzać nasze klasy implementujące interfejs DAO konkretnej encji. Dzięki jej użyciu uzyskujemy o wiele łatwiejszy dostęp do sesji (po to właśnie skonfigurowaliśmy session factory), co prawda może to momentami być problematyczne ( o tym pewnie kiedy indziej napiszę ), dla obecnego przykładu powinno być OK.
- HibernateDaoSupport wraz z obiektem pomocniczym HibernateTemplate – jest pokłosiem powyższego – również rozszerzamy klasę HibernateDaoSupport, ale za to odowłujemy się również do obiektu pomocniczego HibernateTemplate, co znacznie skraca kod.
Jako, że jedno wynika z drugiego pokażę obie implementacje. W wersji pierwszej po prostu rozszerzymy klasę HibernateDaoSupport:
package com.wordpress.chlebik.dao.implementation; import com.wordpress.chlebik.dao.interfaces.NewsDaoInterface; import com.wordpress.chlebik.domain.News; import java.util.List; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; /** * Klasa DAO dla newsów * * @author chlebik */ public class NewsDao extends HibernateDaoSupport implements NewsDaoInterface { List<News> newsy; SessionFactory sessionFactory; /** * Bezargumentowy konstruktor */ public NewsDao() {} /** * Wyciaga newsy w zadanej ilosci * * @param int counter * @return List<News> */ public List<News> getNews(int counter) { try { Session session = getSession(); newsy = (List<News>)session.createQuery( "from News order by data desc limit " + counter ).list(); releaseSession( session ); return newsy; } catch ( Exception e ) { System.out.println( e.toString() ); } return newsy; } public List<News> getNewsy() { return newsy; } public void setNewsy( List<News> lista ) { newsy = lista; } }
Jak widać łatwiej dostać się do sesji, łatwiej ją uwolnić i w ogóle jest zawsze czysto, zawsze sucho i powiedzmy, że zazwyczaj pewnie. Jednakże kod ten można jeszcze bardziej uprościć i doprowadzić do takiej oto postaci (sama metoda getNews()):
public List<News> getNews(int counter) { return (List<News>) getHibernateTemplate().find( "from News order by data desc limit " + counter ); }
Obiekt pomocniczny HibernateTemplate udostępnia kilka najbardziej popularnych metod używanych przy pracach z bazami danych, dlatego też w tak prostych przypadkach jego użycie jest jak najbardziej wskazane. Po więcej informacji na temat tej klasy odsyłam do dokumentacji. Ja użyłem metody find, która jak widać wyciąga rekordy pasujące do zapytania. Uwaga! Używanie tego podejścia jest bardzo szybkie i przyjemne, ale automatycznie mocno skazuje nas na integrację ze Springiem. W przypadku gdybyśmy chcieli zmienić coś w samym kodzie aplikacji i ewentualnie przemigrować jego kawałek na inny framework mielibyśmy trochę roboty. Polecam odnośnik z jednego z komentarzy do tutoriala Darka Zonia ( mały link ).
Cóż nam teraz zostało? Sprawdzić czy w ogóle coś działa! Odwołamy się do istniejącego już kontrolera – IndexController. Tam w metodzie handleRequest do tej pory zwracaliśmy prostą instancję ModelandView. Na razie zostawimy ten temat sam sobie, natomiast stworzymy obiekt DAO i zobaczymy czy na pewno zwrócił to, co powinien zwrócić. Modyfikujemy zatem ww. metodę i dodajemy taki oto kod:
newsy = newsDao.getNews(MAIN_PAGE_NEWS_COUNTER); System.out.println( "Zwroconych newsow " + newsy.size() );
Jak widać do kontrolera dodałem też stałą, która będzie nam wskazywała ile newsów pobrać na główną stronę. W codziennej praktyce rzeczy związane z konkretnym kontrolerem/akcją umieszczam najbliżej jak się da – do tego jako stałą. Jakoś tak bardziej to elegancko wygląda. Choć pewnie w komercyjnym projekcie dla zewnętrznego klienta takie rzeczy powinno się trzymać w zewnętrznym pliku, a konto łatwiejszego utrzymania. Uruchamiamy nasz projekt. I w konsoli wyskakuje krótkie:
Zwroconych newsow 2
Czyli coś poszło tak jak powinno 😉 Oczywiście kwestia jest teraz taka, że wypadałoby owe newsy przedstawić w odpowiedni sposób. Rzecz jasna wpierw trzeba listę z newsami przekazać do widoku. W kontrolerze zatem zamieniamy linijkę z instrukcją zwrotu obiektu ModelandView na następującą:
return new ModelAndView( "index", "newsList", newsy );
I po kilku zabawach z CSSem oraz z modyfikacją widoku:
<#list newsList as News> <div class="col"> <h4 style="color: #FFF2B3;">${News.title}</h4> <h5>${News.data}</h5> <p style="font-size: 10px; line-height: 1.2em;">${News.content}</p> </div> </#list>
Znaczniki są dość samo-opisujące. Mamy zwykłą iterację po liście i ostateczny efekt wygląda w taki oto sposób: