SCJPTester wyświetla newsy – podstawy Springa w praktyce

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:

Advertisements

3 thoughts on “SCJPTester wyświetla newsy – podstawy Springa w praktyce

  1. kapitan b

    * Klasa domenowa dla newsa w serwisie
    To polski czy angielski?
    Po ch komentować konstruktor “Konstruktor” i w ogóle po co Ci domyślny jeśli pusty?
    Po ch komentować settery/gettery?
    List getNews(int counter) -> counter? Czy counter to coś, co kojarzy się z liczbą (news jest policzalny, więc liczba, a nie ilość, chyba że przyniesiesz mi 3kg newsów) -> number? newsToGet? Po co tworzyć komentarz do czegoś tak prostego, skoro możesz dobrą nazwą załatwić komentarz?

    /**
    * Bezargumentowy konstruktor
    */
    public NewsDao() {}

    o, rly?!

  2. chlebik Post author

    1. Co do “angielskawego” – nie pisze w sumie ksiazki do druku, a blog, tak wygodniej/szybciej/latwiej (niepotrzebne jak zawsze skreslic)
    2. Komentarze JavaDOCa wrzuca mi Netbeans to i cos tam wpisuje. Jak zaczne sie bawic narzedziami do automatycznego generowania dokumntacji to bede mial na czym cwiczyc.
    3. “Counter” – od zawsze (wliczajac Turbo Pascala z 13 lat temu) nazywam tak wszystkie parametry, w ktorych w ten sposob podaje ile konkretnych rzeczy mam zwrocic.

  3. Michał Mech

    Moje trzy grosze. Ogólnie rzecz biorąc komentarze w dużej większości to zuo 😉

    Zmiana sygnatury (a raczej nazwy parametru) omawianej metody na getNews(int limit) byłoby dobrym posunięciem.

    Zgodnie z ruchem Software Craftsmanship powinno się poświęcać czas na czynienie kodu bardziej czytelnym niż na jego komentowanie.

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