Latest Entries »

Wednesday, August 5, 2009

Hibernate, <h:dataTable> i usuwanie rekordów

Po tylu godzinach spędzonych przy debugowaniu i szukaniu rozwiązania na mój problem dotyczący połączenia frameworka Hibernate oraz JSF dataTable pomyślałem sobie, że napiszę quick how-to odnośnie tego problemu.

Problem podzielę na kilka części:
  1. Pierwszą częścią będzie utworzenie relacji w bazie mysql-owej i utworzenie odpowiednich tabel do dalszej pracy.
  2. Utworzenie aplikacji w NetBeans oraz konfiguracja Hibernate.
  3. Ostatnia część będzie odpowiedzialna za właściwy problem. Tutaj udostępnię kod i go wytłumaczę.
*Jeżeli umiesz wykonać daną część to spokojnie możesz ją pominąć.


Część 1.

Szukając odpowiedzi na tworzenie relacji w mysql napotkałem się na bardzo treściwą stronę,
na której jest wszystko naprawdę ładnie wytłumaczone. Zapraszam do kliknięcia poniżej.


No dobra to napewno już masz utworzony widok relacyjny w phpmyadmin. Teraz trzeba utworzyć bazę danych (nazwałem ją hibernate) oraz tabele (person, orders).

CREATE TABLE IF NOT EXISTS `person` (
`ID_PERSON` int(11) NOT NULL AUTO_INCREMENT,
`NAME` varchar(100) COLLATE utf8_polish_ci NOT NULL,
`SURNAME` varchar(100) COLLATE utf8_polish_ci NOT NULL,
PRIMARY KEY (`ID_PERSON`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci AUTO_INCREMENT=32 ;


Tabela orders:
CREATE TABLE IF NOT EXISTS `orders` (
`ID_ORDER` int(11) NOT NULL AUTO_INCREMENT,
`ID_PERSON` int(11) NOT NULL,
`TITLE` text COLLATE utf8_polish_ci NOT NULL,
`COST` float NOT NULL,
PRIMARY KEY (`ID_ORDER`),
KEY `ID_PERSON` (`ID_PERSON`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci AUTO_INCREMENT=1 ;

// utworzenie odpowiedniej relacji między person.ID_Person (a kluczem obcym)<->
orders.ID_Person

ALTER TABLE `orders`
ADD CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`ID_PERSON`) REFERENCES `person` (`ID_PERSON`) ON DELETE CASCADE ON UPDATE CASCADE;
Teraz gdy masz puste tabele to przejdź w phpmyadmin do tabeli orders i spróbuj dodać nowy rekord. Jeżeli w tabeli person nie ma rekordu to wystąpi błąd sql ponieważ relacja między tabelami nie pozwala na dodawanie do orders rekordów gdy person nie ma danych.
Lekko zboczyliśmy z kursu, pardon.
Mała rada:
Dodaj do tabeli person jak najwięcej osób, a następnie kliknij zakładkę eksport i wyeksportuj dodane rekordy do pliku person.sql. Przyda Ci się to przy testowaniu gdy zabraknie rekordów w tabeli nie będziesz musiał ręcznie ich wprowadzać tylko uruchomisz plik person.sql, a tabela automatycznie będzie znów pełna.

Część 2.

Zatem czas przystąpić do uruchomienia NB (ja aktualnie pracuje w wersji 6.7).
Kliknij: New Project -> Java Web -> Web Application -> nazwa projektu: hibernateOnMysql -> Server and Settings : Server Glassfish 2.1, EE Version: Java EE 5 -> Frameworks : JavaServer Faces i Hibernate 3.2.5, ustaw Database Connection na : jdbc:mysql://localhost:3306/hibernate -> Finish.

Gdy kliknąłeś finish powinien pojawić Ci się plik hibernate.cfg.xml : kliknij zakładkę: Miscellaneous Properties, add -> Property name : hibernate.current_session_context_class, property value : Thread.
Teraz przejdź do zakładki : Configuration Properties -> add : Property Name: hibernate.show_sql, property value : true .

Pierwszy krok mamy za sobą teraz trzeba utworzyć kolejne pliki konfiguracyjne:

Wybierz zakładkę hibernate:


Dalej add all table:



Naszym oczom pokazał się plik: hibernate.revenge.xml ,z którego widać, które tabele będą następnie poddane mapowaniu.

Teraz netbeans automatycznie utworzy klasy Java oraz pliki hbm.xml dla każdej tabeli z bazy danych. W tym celu ponownie kliknij new -> other -> hibernate i wybierz New Hibernate Mapping Files and POJOs from Database.


W package wpisz hibernate . Kliknij finish.













Otwórz zakładkę Source Packages i hibernate i otwórz klasy Person.java i Orders.java i odpowiadające im pliki hbm.xml :


Możesz zauważyć jak wygląda mapowanie klas Java z tabelami z bazy hibernate.

Konfigurację aplikacji zakończymy dodając do faces-config.xml odpowiednie klasy:

Część 3.

W tym miejscu będziemy musieli na stronie JSP dodać znacznik a w nim zagnieździć odpowiednio oraz zaimplementujemy klasę MyDataModel, która będzie dziedziczyć ListDataModel i zaimplementujemy metodę interfejsu javax.faces.event.ActionListener o nazwie processAction().

Zatem tak powinien wyglądać komponent :



Musimy zdefiniować atrybut value i związać go z własnością tableValues klasy MyDataModel. W naszej klasie będzie to obiekt ArrayList<Person>, który będzie przechowywał wszystkie osoby pobrane z tabeli Person. Oznaczamy tableValues zmienną osoba. W każdej kolumnie tworzymy znacznik <f:outputText/> i ustawiamy jego atrybut value na odpowiednie pole zmiennej osoba. <f:facet> tworzymy w celu dodania nagłówków dla tabeli.
W ostatniej kolumnie natomiast dodajemy przyciski commandButton i do komponentu dodajemy atrybut idPerson o wartości osoba.idPerson i tworzymy słuchacza akcji
<f:actionListener type="classes.MyDataModel" /> informując, że klasa MyDataModel implementuję odpowiednią metodę processAction() . Atrybut action wskazuje
na metodę MyDataModel.delete(), które de facto nic nie będzie robić tylko zwraca null :-).

Teraz po wytłumaczeniu komponentu h:dataTable trzeba zaimplementować odpowiednie metody aby wszystko działało poprawnie. Obie własności tableValues i idPerson powinny być private zamiast public ...
Widzimy metodę getTableValues, która wykonuje proste zapytanie hsql pobierające wszystkie osoby.





Teraz kod obsługujący zdarzenie kliknięcia:



57. Pobieram komponent, który wywołał akcję klikniecia.
58. Pobieram wartość atrybutu z znaczika commandButton o nazwie idPerson.
59-64. Kod odpowiada za usunięcie danej osoby.
65-69. Najważniejsze fragmenty kodu.

Teraz wytłumaczę dlaczego metoda redirect() jest tak ważna. Otóż gdy klikniemy przycisk usuń to formularz jest wysyłany na serwer, który nastepnie usuwa dany rekrod. Taka funkcjonalność nam odpowiada w 100%. Niestety odświeżenie strony (F5) powoduje ponowne wysłanie formularza na serwer. Formularz ten również niestety przez implementację JavaServer Faces zapisuje wszystkie dane żądania na serwerze i w fazie przywrócenia widoku przesyła je "spowrotem do przeglądarki". Gdy odświeżymy stronę atrybut idPerson zwiększa wartość o 1. Osobiście nie mam pojęcia dlaczego, ale podczas działania i debugowania zauważyłem tą własność.
Po każdej akcji kliknięcia wykonanie przekierowania na tę samą stronę skutkuje tym, że bieżące żądanie zostaje wstrzymane, a kontener JSP odsyła przekierowanie HTTP. Zatrzymanie bieżącego żądania skutkuje tym, że wszystkie dane formularza są po prostu tracone. Następnie gdy użytkownik ponownie odświeży stronę to formularz nie będzie zawierał parametru idPerson. Przy tej implementacji nie musimy martwić się o zdublowaną akcje usuwania rekordów. Druga zaleta takiego rozwiązania to, że po akcji redirect strona jest ponownie konstruowana i wywołana metoda getTableValues(). Dzięki temu nie musimy sami dbać o zmianę zawartości modelu ListDataModel i metod get/setWrappedData().

0 comments:

Post a Comment