Latest Entries »

Wednesday, August 26, 2009

Rysowanie wykresów w html.

Niezależnie od technologii, w której będziemy programować zdarza się czasem potrzeba wyświetlenia informacji w postaci wykresów. Czy to pChart dla php, a może ICEFaces i odpowiednie znaczniki. Programista ma duży dostęp do gotowych bibliotek w każdym języku programowania. Wyobraźmy sobie, że nauczyłem się specyfikacji pChartu czy JFreeChart i moje aplikacje tworzą piękne wykresy. Jednak co się zdarzy gdy jestem zmuszony skorzystać z technologii, w której nie znam podstaw, a pracodawca każe utworzyć strony z wykresami? Czas goni, nerwy się wdzierają w każdy fragment kodu, a my niechlujnie zapisujemy różne metody i klasy. Nie testujemy bo nie ma czasu... Dodatkowo generowanie danych binarnych obciąża nasze serwery i zapycha ruch.

No i co począć?

Znalazłem ciekawe rozwiązanie, a dokładniej Google Chart API. Dzięki temu narzędziu nie musimy martwić się o znajomość danego czy innego języka. Idea tworzenia wykresów polega na wysłaniu porcji danych(które chcemy wizualizować) za pomocą metody GET . Adres, który utworzyliśmy i podpinamy pod odpowiedni znacznik img , h:graphicImage lub dowolnie innego.

Przykłady:

a


a

a

a


Nawet mapy można łatwo tworzyć:

a

servlet-name,servlet-mapping, url-pattern i /faces

Kilka dni temu gdy kolejny raz przeglądałem książkę o JavaServer Faces przed snem doznałem olśnienia dlaczego trzeba wpisywać http:// localhost:8080/mojaAplikacja/faces/strona.jsp aby strona strona.jsp wyświetliła prawidłowo. Chciałbym zaprezentować przykład krok po kroku, a w trakcie będę tłumaczył uzyskane wyniki. Przykład zrealizuję w środowisku Netbeans 6.7.1
Zatem zaczynamy:

*Dodaj framework JavaServer Faces i kliknij finish.

Po stworzeniu przykładowej aplikacji ServletFaces otrzymujemy następującą strukurę katalogów:



Gdy uruchomimy aplikację wyświetli się nam w oknie przeglądarki adres:

Teraz spróbujemy dodać stronę jsp np. page2.jsp. Struktura katalogów powinna wyglądać tak:


Uzupełnij stronę tak abyś miał pewność, że znajdujesz się na stronie page2.jsp. Ja uzupełniłem ją w następujący sposób:



Teraz będziemy starali się poprzez adres w przeglądarce wyświetlić treść nowo utworzonej strony. Pierwszym pomysłem byłoby wpisanie następującego adresu:


Jeżeli odwiedzasz ten artykuł to z pewnością wiesz jaki będzie rezultat. Pytanie tylko dlaczego?

org.apache.jasper.JasperException: java.lang.RuntimeException: Cannot find FacesContext
W skrócie:

Powodem tego jest sposób wdrażania aplikacji internetowych, które są tak naprawdę procesem żmudnym. Trzeba utworzyć odpowiednią strukturę katalogów (patrz powyżej) oraz odpowiednio skonfigurować pliki (*.xml) aplikacji. Za dynamiczną treść prezentowaną na stronach jsp są odpowiedzialne Servlety. Servlety to klasy java, które przetwarzają zlecenia klientów.

Servlety konfiguruje się w plikach xml podając dane potrzebne do uruchomienia. Zajrzyj do pliku web.xml. W tym pliku znajduje się odpowiedź dlaczego wcześniejsze http://localhost:8080/ServletFaces/page2.jsp wywołało wyjątek.

Oto odpowiedź:


Widzimy jak się prezentuje nazwa servletu: Faces Servlet oraz, która klasa implementuje metody inicjalizujące, wykonujące żądania oraz "niszczące" dany servlet:
javax.faces.webapp.FacesServlet.
Podświetlony na żółto znacznik <servlet-mapping/> odpowiada za "uruchamianie" i wykonywanie kodu FacesServlet.

<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>

Powyższy kod mówi nam: że Faces Servlet będzie uruchamiany w przeglądarce jeśli wpiszemy adres zawierający url-pattern. Na przykład aby wyświetlić wcześniejszą stronę page2.jsp musimy wpisać następujący adres:


Teraz skoro rozwiązaliśmy początkowy problem dodamy nową regułę url-pattern, która nieco zmieni sposób w jaki będziemy mogli uruchomić serwlet. Do pliku web.xml w miejscu zakończenia poprzedniego tagu servlet-mapping dodaj następujący kod:


Teraz zamiast pisać:
Możemy alternatywnie wywołać stronę jsp w poniższy sposób:

Dziękuję za uwagę :-)


Benone!

Wednesday, August 12, 2009

Poziomy izolacji tranzakcji i Hibernate.

Próbując sił z Hibernate musiałem natknąć się na problem zwany Tranzakcje i Sesje. Ku mojemu zdziwieniu znalazłem na polskich stronach ciekawe omówienie tematyki tranzakcji SQL, które są "kręgosłupem" podczas programowania w Java i Hibernate. Podsyłam link:

Sunday, August 9, 2009

ASP.NET , C#, LINQ to SQL i unikalne rekordy z tabeli.

Istnieje wiele różnych sposobów, które są lepsze lub gorsze aczkolwiek ja przedstawię taki, który jest bardzo szybki w implementacji(w działaniu pewnie odwrotnie proporcjonalny). Pierwszą rzeczą, którą należy wykonać to pobrać wszystkie rekordy z bazy danych np przy pomocy LINQ to SQL Classes.

Przykładowo jeśli mamy tabelę w MSSQL o nazwie np. Odleglosci, która posiada primary key ID, oraz kolumnę miastoA, miastoB oraz odleglosci.
Pobieram wszystkie (powtarząjące się) rekordy miastoA:

var miasta = (from ex in mojDataContext.Odlegloscis select ex.miastoA);

Następnie skorzystamy z klasy HashSet, która jest kontenerem reprezentującym zbiór elementów w naszym przypadku obiektów String.
HashSet<String> uniqeMiasta = new HashSet();
Następnie przejdziemy po każdym "rekordzie" wynikowym z zmiennej miasta:

foreach (String o in Miasta)
{
uniqeMiasta.Add(o);
}


Następnym krokiem(ostatnim) będzie utworzenie tablicy oraz posortowanie jej alfabetycznie.
String[] miasta = uniqeMiasta.ToArray();
Array.Sort(miasta);


Wyjaśnię teraz na czym polega fenomen obiektów HashSet. Po prostu implementacja tej klasy uniemożliwia wstawienie dwóch identycznych wartości poprzez metodę .Add(Object o).

Dziękuję za uwagę.

Hibernate, pobranie kilku kolumn z tabeli

Dziś opiszę trochę technologię Hibernate czyli frameworka, który zajmuje się mapowaniem obiektów Java na tabele z bazy danych. O ile pobranie zawartości całej tabeli nie jest trudnym zadaniem to wyświetlenie kilku kolumn może przysporzyć programiście trochę pracy. Najpierw szybko przypomnę jak pobrać i wyświetlić całą tabelę, a następnie pokażę jak pobrać poszczególne kolumny.

Zakładam, że klasa HibernateUtil, klasy odwzorowywujące tabele, pliki hbm.xml, są już utworzone w Twoim projekcie.
Dla naszego przykładu zdefiniujmy klasy java np.: Osoba

public class Osoba implements Serializable
{
  private int ID_Osoby;
  private String imie;
  private String nazwisko;
  public Osoba(){}//Domyślny konstruktor JavaBeans.
  // settery i gettery dla każdego 
  pola.:getImie,setImie,getID_Osoba,set_ID_Osoba,setNazwisko,getNazwisko;
}

Zatem po pierwsze tworzymy obiekt sesji i tranzakcji Hibernate:

  Session session = HibernateUtil.getSessionFactory().getCurrentSession();
  Transaction tx = session.beginTransaction();
  tx.begin();
Czas teraz przygotować odpowiednie zapytanie hsql, które pobierze całą tabelę: from Osoba.

  Query Q = session.createQuery("from Osoba");
  ArrayList<Osoba> list= new ArrayList<Osoba>();
  list = (ArrayList<Osoba>) Q.list();
  tx.commit();
Widać, że utworzyłem zmienną list, która jest typu ArrayList z parametrem Osoba. Parametrem jest typ Osoba , ponieważ pobrałem wszystkie kolumny tabeli. Możemy teraz pobrać list.get(int i) poszczególną Osobę i wyświetlić jej informacje na konsoli:
for (int i=0;i<list.size();i++)
{
  Osoba tempOsoba = list.get(i);
  System.out.println(tempOsoba.getID() + " " + tempOsoba.getImie() + " " +   
  tempOsoba.getNazwisko());
}
To był tylko wstęp. Teraz przejdziemy do prawdziwego zadania czyli wyświetlenie 2 kolumn imie oraz nazwisko z tabeli Osoba.

  Session session = HibernateUtil.getSessionFactory().getCurrentSession();
  Transaction tx = session.beginTransaction();
  tx.begin();
  Query Q = session.createQuery("select imie,nazwisko from Osoba");
  //Można zapisać "select osoba.imie,osoba.nazwisko from Osoba as osoba" - bardziej obiektowo :-)
  ArrayList<Object[]>list = new ArrayList<Object[]>;
  for (int i=0;i<list.size();i++)
  {
    Object temp[] = list.get(i);
    System.out.println(temp[0] + " " + temp[1]);
  }

Przydało by się wyjaśnić powyższy fragment kodu. Chodzi o to, że jeśli wybieramy kilka kolumn to implementacja Hibernate najprościej ujmując każdą kolumnę(każdy rekord kolumny jest typu Object) pakuje w obiekt Object. Jeżeli po klauzuli select wystąpią dwie[lub więcej] kolumny np : imie, nazwisko to automatycznie te dwie wartości są udostępniane jako tablica Object[] a dostęp do pól mamy za pomocą indeksów. Dlatego musimy utworzyć odpowiednią listę ArrayList<Object[]> do odebrania danych z rekordów z tabeli.

Można też użyć iteratora dla listy:

for (Iterator i = list.iterator();i.hasNext();){
  Object[] result = (Object[]) i.next();
  System.out.println(result[0] + " " + result[1] );
}

Dziękuję za uwagę - Robert.

Wednesday, August 5, 2009

Zapisanie, serializacja obiektów Java


Środowisko programistyczne java pozwala na kilka sposobów zapisania obiektu do "źródła", które po zamknięciu programu będzie przechowywać wartości obiektów. Do głowy przychodzą mi dwa pomysły

  • łatwy - zapisać stan obiektu w pliku tekstowym zapisując każde pole

  • łatwiejszy - wykorzystać mechanizm serializacji obiektu.


  • Mając na myśli łatwiejszy rozważam tutaj korzyści z serializacji (szybkości implementacji oraz elastyczności programowania).

    Serializacja obiektów w java jest swego rodzaju zrzutem obiektu z pamięci RAM to strumienia bajtowego zapisanego na np dysku twardym. Do tego rodzaju operacji są wykorzystywane klasy strumieniowe : ObjectOutputStream, ObjectInputStream oraz odpowiednie metody void writeObject(Object o), Object readObject(). Standardowo klasy, które są dostępne w javie od razu posiadają metody do zapisu/odczytu wartości danego typu oraz co ważne: implementują intefrejs Serializable np:
    writeUTF(String s) / readUTF()

    writeFloat(float f) / readFloat()
    writeDobule(double d) / read Double()

    etc...

    Zatem jak zapisać/odczytać ?



    Podrzucam spakowany projekt NetBeans SerializationExample.

    Najpierw trzeba utworzyć obiekt ObjectOutputStream aby móc w nim zapisać dane, które nas interesują.



    // Metoda do zapisu danych w pliku test.temp
    try
    {
    ObjectOutputStream MyObjectOutputStream = new ObjectOutputStream(new FileOutputStream("test.temp"));
    MyObjectOutputStream.writeUTF(serializujTextField.getText());
    MyObjectOutputStream.close();
    }
    catch (IOException ex)
    {
    ex.printStackTrace();
    }


    Następnie gdy zapisaliśmy w pliku test.temp spróbujmy zamknąć program, a po uruchomieniu wcisnąć przycisk odczytaj.
    Udostępnię teraz kod, który odczytuje z pliku test.temp naszą zapisaną wartość w String.


    try {
    ObjectInputStream OIS = new ObjectInputStream(new FileInputStream("test.temp"));
    serializujLabel.setText(OIS.readUTF());
    OIS.close();
    }
    catch (IOException ex)
    {

    }


    Działa !

    Jednak dla bardziej złożonych problemów nie wystarczy zapisać samego pola obiektu (String), a trzeba zapisać cały obiekt własnej klasy wraz z jego wszystkimi polami typów prostych (int,char,boolean,float,itp) oraz pola referencyjne (MojaJakasKlasa,JFrame,JButton, itp itp). Zatem stwórzmy klasę o nazwie Osoba, która będzie przechowywać dwa pola imię, nazwisko.

    Przykładowy kod klasy z setterami, getterami i konstruktorem:



    public class Osoba{

    private String imie;
    private String nazwisko;

    public String getImie() {
    return imie;
    }

    public void setImie(String imie) {
    this.imie = imie;
    }

    public String getNazwisko() {
    return nazwisko;
    }

    public void setNazwisko(String nazwisko) {
    this.nazwisko = nazwisko;
    }


    public Osoba(String imie, String nazwisko) {
    this.imie = imie;
    this.nazwisko = nazwisko;
    }
    }


    Skoro dodałem już klasę Osoba to niech obiekt tej klasy znajdzie się w głównej formie MyJFrame i zostanie utworzony jeśli naciśniemy przycisk serializuj w panelu Serializacja obiektu Osoba. Następnie zamknijmy program i włączmy go ponownie i kliknijmy odczytaj. Widać, że nie możemy odczytać z ObjectInputStream ponieważ nasza klasa nie implementuje intefrejsu Serializable.
    Powstanie wyjątek typu:

    java.io.InvalidClassException: serializationexample.Osoba; serializationexample.Osoba; class invalid for deserialization


    Zatem musimy zmienić nagłówek klasy na następujący:


    public class Osoba implements Serializable
    .
    Zapiszmy zmiany w projekcie, utwórzmy obiekt osoba wpisując imię i nazwisko w polach tekstowych, kliknijmy serializuj, następnie zamknijmy, otwórzmy program i wciśnijmy przycisk odczytaj. Widać, że program już działa jak powinien.
    Jedyną znaczną różnicą wobec przykładu z typem String jest to, że musimy dokonać konwersji zawężającej do typu Osoba:




    o = (Osoba) MyObjectInputStreamOsoba.readObject();
    MyObjectInputStreamOsoba.close();
    imieLabel.setText(o.getImie());
    nazwiskoLabel.setText(o.getNazwisko());




    Dodam na koniec ciekawostkę, że intefrejs Serializable nie posiada żadnych metod do przedefiniowania i nazywa się znacznikowym intefrejsem. Potrzeba istnienia takich intefrejsów polega na tym, że wyznaczają typ naszej klasy:

    if (O instance of Serializable)

    Powyższa linijka kodu zwróci wartość true, a kompilator będzie wiedział, że obiekty można zapisywać/odczywać.

    Jeżeli mamy rozbudowaną hierarchię dziedziczenia oraz klasa Osoba posiada inne pola obiektowe np. Adres a, HistoriaChoroby h, to nie musimy się martwić o zapisanie tych pól albowiem java rekurencyjnie zapisuje wszystkie pola klasy (muszą oczywiście implementować interfejs Serializable).

    Jeśli mamy bardzo rozbudowaną klasę, która używa mechanizmu szeregowania(serializacji), a nie podoba się nam, że wszystkie pola są zapisywane możemy w prosty sposób powiedzieć kompilatorowi aby nie zapisywał danego pola klasy. Do tego celu musimy użyć słowa kluczowego javy transient przykład:


    private transient String maloWaznaZmienna;

    Orginalny post z mojej domeny rpiesnikowski.pl

    Dziękuję za uwagę i pozdrawiam
    Robert.

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().