niedziela, 27 marca 2011

Internetowa Aplikacja w Eclipse cz. 1

W przypływie kilku wolnych chwil postanowiłem wrzucić nowy post na mojego bloga. Postaram się w poniższym tekście opisać typową aplikację typu Witaj Świecie stworzoną przy pomocy Eclipse z wykorzystaniem JPA i JSF. Końcowym etapem będzie odpalanie aplikacji na serwerze Apache Tomcat. Jako bazę danych wykorzystam popularny silnik bazodanowy MySQL.

Pierwszym krokiem jest ściągnięcie wszystkich potrzebnych rzeczy takich jak:
Eclipse - http://www.eclipse.org/downloads/ - wybieramy wersję dla programistów Java EE
Sterownik do bazy danych MySQL ze strony - http://www.mysql.com/downloads/connector/j/
Bibliotekę JSTL - http://jstl.java.net/download.html – pobieramy dwa pliki: JSTL Api oraz JSTL Implementation
Serwer na którym odpalimy naszą końcową aplikację - http://tomcat.apache.org/download-70.cgi – najnowsza wersja 7.x powinna dać radę :P

Po pobraniu wszystkiego rozpakowujemy paczkę z Eclipse powiedzmy na dysku c:\javaee\eclipse. Odpalamy aplikację i wskazujemy folder c:\javaee\workspace jako folder gdzie chcemy zapisywać nasze wszystkie projekty. Po otworzeniu Eclipse klikamy w New wybieramy Other... i z listy zaznaczamy Dynamic Web Project znajdujący się w katalogu Web. Zatwierdzamy klikając w Next i przechodzimy do kolejnego okna. Wpisujemy nazwę projektu HelloWorld w polu Project Name. W Configuration wybieramy custom i klikamy w Modify. Na liście zaznaczamy: Dynamic Web Module, JavaServer Faces oraz JPA. Zatwierdzamy klikając w OK. Następnie dwa razy klikamy w Next. W kolejnym oknie zaznaczamy Generate web.xml deployment descriptor i przechodzimy dalej klikając Next. Kolejne okno pozwala na skonfigurowanie podstawowych parametrów JPA, które użyjemy w naszej aplikacji. W sekcji JPA implementation klikamy w ikonkę oznaczoną jako: Download library... Z listy jaka się nam ukazała wybieramy EclipseLink 2.1.2 – Helios i klikamy w Next. Akceptujemy postanowienia umowy i klikamy w Finish. Rozpoczęty proces pobierze wybraną przez nas implementację technologii JPA. Po pobraniu powinniśmy powrócić do poprzedniego okna w którym klikamy w Add connection... Wybieramy z listy oczywiście MySQL, nazywamy połączenie HelloMySQL i klikamy w Next. W otwartym oknie klikamy w ikonkę New Driver Definition i z listy zaznaczamy MySQL JDBC Driver w wersji 5.1 (podejrzewam że taką pobraliście bądź nowszą). W zakładce JAR List klikamy Clear All i Add JAR/Zip. Wybieramy pobrany wcześniej sterownik, który należy rozpakować na c:\javaee\mysql-connector-java-5.1.15-bin.jar a następnie wybrać. (Wybieramy archiwum jar a nie zip!). Zatwierdzamy OK. W otwartym oknie konfigurujemy połączenie z serwerem bazodanowym. Zaznaczamy Save password i sprawdzamy czy podaliśmy prawidłowe dane klikając w Test Connection. Klikamy w Next i Finish. Kolejne okno pozwala na konfigurację JavaServer Faces. Musimy wskazać konkretną implementację tej technologii. Niestety jej nie pobraliśmy więc zmuśmy Eclipse do zrobienia tego za nas. Klikamy w Download library i zaznaczamy z listy JSF 2.0 Apache Myfaces JSF Core... Klikamy Next akceptujemy postanowienia umowy i klikamy Finish. Biblioteka JSF zostanie pobrana automatycznie. Klikamy w Finish. To już koniec procesu tworzenia nowej aplikacji. Jeżeli pojawiła się nam duża powitalna plansza zamykamy ją, a naszym oczom ukaże się godne polecenia środowisko programistyczne z otwartym projektem.

Następny proces to wrzucenie plików: mysql-connector-java-5.1.15-bin.jar, jstl-api-1.2.jar oraz jstl-impl-1.2.jar do katalogu: WebContent/lib. Jest to najprostsza forma dołączenia zewnętrznych bibliotek do projektu. W kolejnej części wpisu powiem w jaki sposób zmusić serwer, aby w momencie włączania nawiązał połączenie z bazą danych i podczas wyłączenia również się rozłączył. Aby zaimplementować klasę słuchacza należy do projektu dorzucić jeszcze jeden plik jar, bez którego ani rusz. Klikamy prawym przyciskiem myszy na projekt i wybieramy Properties. Przechodzimy do zakładki Java Build Path i klikamy Add External JARs. Przechodzimy do katalogu gdzie rozpakowaliśmy serwer Tomcat i wybieramy katalog lib. Wybieramy plik servlet-api.jar. To sprawi że ta biblioteka będzie widoczna w naszym projecie. Drugim krokiem to edycja deskryptora wdrożenia znajdującego się w pliku WebContent/WEB-INF/web.xml. Usuwamy najlepiej wszystko co w nim znajdziemy i wklejamy poniższy kod:


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>HelloWorld</display-name>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>


Plik ten pozwala na skorzystanie z technologii JSF w naszej aplikacji i jednocześnie wskazuje w jaki sposób będzie się można „dostać” do naszych podstron oraz jaka podstrona będzie domyślnie ładowaną odpowiedzią. Stwórzmy pierwszą podstronę wraz z ziarnem, które podłączymy razem. Klikając prawym przyciskiem myszy na nasz projekt wybieramy New, Other, Web i HTML File. Zatwierdzamy przyciskiem Next i wpisujemy nazwę nowego pliku: index.html i kończymy klikając Finish. Stwórzmy również klasę, w której znajdzie się logika naszej aplikacji ponowie klikając prawym klawiszem wybierając New a następnie Class. Uzupełniamy pole package wpisem: org.axlinuxprogramming.bean i nazywamy naszą klasę: MyBean. Każde ziarenko, z którego korzystamy powinno spełniać kilka prostych reguł:
  • powinno posiadać domyślny publiczny konstruktor – nie przyjmujący innych parametrów.
  • Każde pole jest prywatne i powinno zawierać metody pozwalające zapisać oraz odczytać tą wartość – geter i seter.
  • Klasa powinna być Serializowalna, choć nie zawsze jest to potrzebne.

Zanim wszystko ze sobą ładnie połączymy dokończmy sprawę z JPA. Otwieramy plik persistence.xml znajdujący się w: src/META-INF. Wklejamy do niego poniższą konfigurację. Plik ten zawiera podstawowe informacje w jaki sposób połączyć się z bazą danych, a także np. klasy encji. Poniższy plik oczywiście modyfikujemy tak, aby odpowiadał wymogom naszej bazy danych.


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="HelloWorld" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/helloworld"/>
<property name="javax.persistence.jdbc.password" value="QwertY"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="helloworld"/>
<property name="eclipselink.ddl-generation" value="create-tables"/>
</properties>
</persistence-unit>
</persistence>


W pliku możemy zdefiniować kilka połączeń z bazą danych. Przykładem może być sytuacja, w której z innej bazy korzystamy podczas tworzenia aplikacji, a z innej gdy wdrażamy aplikację. Pojedynczą konfigurację definiujemy pomiędzy znacznikami persistence-unit. Nasze połączenie nosi nazwę HelloWorld i oprócz danych o połączeniu zawiera informację o tym, że JPA ma utworzyć tabele w przypadku gdy klasy encji będą istnieć, a tabele nie. Stwórzmy jeszcze jedną klasę, nazwijmy ją DBHelper i umieśćmy w pakiecie org.axlinixprogramming.db. Klasa ta będzie odpowiadać za całą ceremonię łączenia się z bazą danych, inicjowania połączenia, a także jego zakończenia. Klasa ta oparta jest o wzorzec projektowy – singleton. Dzięki czemu mamy jedynie jedną instancję klasy DBHelper dla całej aplikacji. Sam proces tworzenia EntityManagerFactory jest czasochłonny i wystarczy, aby istniał jeden obiekt dla całej aplikacji. Dzięki tej klasie pozbyliśmy się problemu z „wstawaniem” całego JPA podczas wywołania pojedynczego żądania
oraz uchroniliśmy się przed zagrożeniami czyhającymi na nas w aplikacjach wielowątkowych.



package org.axlinuxprogramming.db;


import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;


public class DBHelper
{
private static DBHelper instance;
private EntityManagerFactory emf;

private final String UNIT_NAME = "HelloWorld";

private DBHelper(){}

public synchronized static DBHelper getManager()
{
if(instance == null)
instance = new DBHelper();

return instance;
}

public EntityManagerFactory createEntityManagerFactory()
{
if(emf == null)
emf = Persistence.createEntityManagerFactory(UNIT_NAME);

return emf;
}

public EntityManager createEntityManager()
{
return createEntityManagerFactory().createEntityManager();
}

public void closeEntityManagerFactory()
{
if(emf != null)
emf.close();
}
}



 Pytanie teraz brzmi jak poradzić sobie z wstaniem JPA podczas uruchomienia serwera i jak zakończyć prawidłowo jego działanie gdy serwer będzie wyłączany. Rozwiązaniem jest stworzenie słuchacza, który wykryje gdy serwer budzi się do życia bądź będzie zmierzał ku wyłączeniu i wykona nasz kod. Stwórzmy jeszcze jedną klasę o nazwie HelloWorldListener w pakiecie org.axlinux.listener i wrzućmy do niej taki oto kod:




package org.axlinux.listener;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.axlinuxprogramming.db.DBHelper;

public class HelloWorldListener implements ServletContextListener
{

@Override
public void contextDestroyed(ServletContextEvent arg0)
{
//Zamykam połączenie z bazą
DBHelper.getManager().closeEntityManagerFactory();
}

@Override
public void contextInitialized(ServletContextEvent arg0)
{
//Rozpoczynam połączenie z bazą
DBHelper.getManager().createEntityManagerFactory();
}
}

Teraz należy dodać jeszcze do web.xml taki oto wpis od razu wewnątrz znacznika: web-app. Pozwoli to na odnalezienie klasy serwerowi i dopalenie naszego kodu podczas procesu włączenia i wyłączenia serwera.


<listener>
<description>HelloWorldListener</description>
<listener-class>org.axlinux.listener.HelloWorldListener</listener-class>
</listener>

Teraz przyszła pora na zabawę – wygenerujmy klasę encję i wstawmy kilka wyników do bazy, a następnie wyświetlmy wszystko na stronie. Klikamy prawym w projekt i wybieramy New, a następnie Other, JPA i zaznaczamy Entity i klikamy Next. W otwartym oknie uzupełniamy nazwę pakietu: org.axlinuxprogramming.entity i wpisujemy klasę: Post. Klikamy następnie w Next. Za pomocą przycisku Add dodajemy kilka kolumn jak: id typu int, title (String), content (String), author (String). Zaznaczamy przy id checkboxa dla kolumny key. Klikając Finish dodajemy nową klasę do projektu. Wygenerowana klasa powinna wyglądać mniej więcej tak jak moją:



package org.axlinuxprogramming.entity;

import java.io.Serializable;
import java.lang.String;
import javax.persistence.*;

/**
* Entity implementation class for Entity: Post
*
*/

@Entity
public class Post implements Serializable {

@Id
@GeneratedValue (strategy = GenerationType.AUTO)
private int id;
private String title;
private String content;
private String author;
private static final long serialVersionUID = 1L;

public Post() {
super();
}

public Post(String title, String content, String author) {
super();
this.title = title;
this.content = content;
this.author = author;
}

public int getId() {
return this.id;
}

public void setId(int id) {
this.id = id;
}
public String getTitle() {
return this.title;
}

public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return this.content;
}

public void setContent(String content) {
this.content = content;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

}



Zwróć uwagę, że dodałem dodatkową adnotację: @GeneratedValue (strategy = GenerationType.AUTO), która zadba o to, aby kolejne rekordy miały unikatowe wartości klucza. Dodałem również konstruktor przyjmujący parametry. Do pliku persistence.xml dodała się również automatycznie pojedyncza linia:


<class>org.axlinuxprogramming.entity.Post</class>


Jest to informacja o klasie, która jest encją w naszej aplikacji. Pora wrócić do klasy MyBean i stworzyć dla niej konstruktor, w którym sprawdzimy czy istnieją jakieś wpisy w naszej tabeli, a jeżeli nie to coś do niej dodamy. Kluczowym obiektem w konstruktorze jest EntityManager. Dzięki niemu jesteśmy wstanie rozpocząć nową transakcję, a także ją zakończyć. Cały kod sql-owy wykonany pomiędzy begin(), a commit() zostanie wykonany dopiero po commit(), a w przypadku gdy pojedyncze zapytanie wysypie się to JPA zadba o to aby wycofać również pozostałe zapytania.




package org.axlinuxprogramming.bean;

import java.util.List;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.axlinuxprogramming.db.DBHelper;
import org.axlinuxprogramming.entity.Post;


@ManagedBean(name="myBean")
@RequestScoped
public class MyBean
{

public MyBean()
{
EntityManager em = DBHelper.getManager().createEntityManager();

em.getTransaction().begin();

Query query = em.createQuery("SELECT COUNT(p.id) FROM Post p");
Number countResult=(Number) query.getSingleResult();

if(countResult.intValue() == 0)
{
Post p1 = new Post("My First Post", "It's my first story...", "Tomasz");
em.persist(p1);
Post p2 = new Post("My Secound Post", "It's my secound story...", "Tomasz");
em.persist(p2);
}
em.getTransaction().commit();
em.close();
}


public List getPosts()
{
EntityManager em = DBHelper.getManager().createEntityManager();

em.getTransaction().begin();

Query query = em.createQuery("SELECT p FROM Post p");
List result = query.getResultList();


em.getTransaction().commit();
em.close();

return result;
}

}


Moje ziarenko posiada również metodę getPosts. Mówiłem, że getery odnoszą się do pól, ale równie dobrze zamiast tego można wykorzystać je na przykład tak jak tutaj do pobrania kolekcji z bazy danych. Dzięki metodzie getPosts możemy „przenieść” kolekcję obiektów pobranych z bazy danych na stronę html. Ale zanim przejdziemy do edycji pliku index.xhtml przyjrzyjmy się adnotacjom @ManagedBean(name="myBean") i @RequestScoped. Pierwsza z nich powoduje, że nie musimy informować JSF o naszych beanach przy pomocy pliku XML. Również nazywamy nasz bean jako myBean i pod taką nazwą będzie on widoczny w aplikacji. Druga adnotacja informuje JSF, że obiekt naszej klasy jest tworzony przy każdym nowym żądaniu. Reasumując za każdym razem gdy odwiedzimy stronę, które korzysta z tej klasy na nowo zostanie utworzony obiekt. W przeciwieństwie do zasięgu sesyjnym, gdzie obiekt jest tworzony osobno dla każdego użytkownika i trwa przez sesję.


Przejdźmy teraz do pliku index.xhtml i wklejmy do niego taki oto kod:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<head>

<title>Insert title here</title>
</head>

<body>

<h2>Oto moje posty</h2>

<h:dataTable value="#{myBean.posts}" var="p" border="1">
<h:column>
<h:outputText value="#{p.id}" />
</h:column>

<h:column>
<h:outputText value="#{p.title}" />
</h:column>

<h:column>
<h:outputText value="#{p.content}" />
</h:column>

<h:column>
<h:outputText value="#{p.author}" />
</h:column>
</h:dataTable>
</body>
</html>




Pod nagłówkiem h2 wrzucamy komponent dataTable. Odpowiada on za wygenerowanie tabeli oraz jest iteratorem, dzięki któremu możemy wrzucić obiekty z kolekcji. Kod: value={#myBean.posts} pobierze z naszego ziarna kolekcję dzięki metodzie getPosts. Każdy element kolekcji zostanie podstawiony za p. Jest to pewnego rodzaju pętla foreach osadzona w dokumencie xhtml. Następnie definiujemy kolejno kolumny i odwołujemy się za pomocą EL-wyrażeń (#{}) do pól obiektów Post.

Teraz kulminacyjny moment – uruchamiamy aplikację na serwerze. Klikamy prawym przyciskiem na projekt wybieramy Run As i Run On Server. Zaznaczamy Manually define a new server i z listy wybieramy Apache, Tomcat v7.0 Server. Klikamy w Add... i w nowym oknie wybieramy lokalizację naszego serwera. Klikamy Finish, Next i Finish. Po poprawnym wystartowaniu serwera powinniśmy zobaczyć całość pod adresem: http://localhost:8080/HelloWorld/index.html.

To już koniec. Mam nadzieję, że również u Was wszystko zadziałało jak należy. Postaram się wrzucić jeszcze kolejne wpisy o tym jak np. edytować i dodawać wpisy w aplikacji – ale to innym razem.