Skip to content

9. Wykorzystanie źródła danych

W niniejszym rozdziale zamierzamy przedstawić podstawy korzystania z baz danych w ramach aplikacji Struts.

9.1. Aplikacja Struts /listarticles

Chcemy wyświetlić zawartość tabeli przechowującej cechy artykułów sprzedawanych przez sklep.

code
kod artykułu – klucz główny
nom
nazwa artykułu
prix
cena artykułu
stockActuel
aktualny stan magazynowy artykułu
stockMinimum
stan magazynowy, poniżej którego
należy zainicjować uzupełnienie zapasów

Zawartość tabeli jest następująca:

Image

Aplikacja listarticles wyświetli nam to samo (choć w gorszej jakości) na stronie internetowej:

Image

9.2. Konfiguracja aplikacji Struts /listarticles

Plik web.xml aplikacji ma standardową postać:

web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
      <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <init-param>
        <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
      <servlet-name>action</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

  <taglib>
      <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri>
    <taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
  </taglib>
  <taglib>
      <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri>
    <taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
  </taglib>

</web-app>

Plik struts-config.xml wprowadza nową sekcję <data-sources>, która umożliwia deklarowanie i konfigurowanie źródeł danych:

struts-config.xml

<?xml version="1.0" encoding="ISO-8859-1" ?>

<!DOCTYPE struts-config PUBLIC
          "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
          "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">

<struts-config>
    <data-sources>
        <data-source key="dbarticles">
            <set-property property="driverClass" value="com.mysql.jdbc.Driver"></set-property>
            <set-property property="url" value="jdbc:mysql://localhost/dbarticles"></set-property>
            <set-property property="user" value="admarticles"></set-property>
            <set-property property="password" value="mdparticles"></set-property>
            <set-property property="minCount" value="2"></set-property>
            <set-property property="maxCount" value="5"></set-property>
        </data-source>
    </data-sources>

    <action-mappings>
        <action path="/liste" type="istia.st.struts.articles.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/listarticles.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
    </action-mappings>

    <message-resources parameter="istia.st.struts.articles.ApplicationResources" 
        null="false" />

</struts-config>

9.2.1. Źródła danych

Sekcja <data-sources> służy do deklarowania wszystkich źródeł danych aplikacji, z których każde jest opisane w sekcji <data-source>. Ten tag obsługuje kilka atrybutów:

key
identyfikuje źródło danych, gdy występuje ich więcej niż jedno.
driverClass
nazwa klasy, którą należy zainicjować, aby uzyskać dostęp do bazy danych. Klasa ta znajduje się zazwyczaj w bibliotece klas (.jar) dostarczonej przez wydawcę SGBD. Biblioteka ta zawiera sterownik JDBC umożliwiający dostęp do bazy danych. Właściwość driverClass określa ten sterownik. Plik .jar zawierający klasy dostępu do SGBD zostanie umieszczony w folderze WEB-INF/lib aplikacji.
url
ciąg połączenia z konkretną bazą danych. Sterownik JDBC umożliwia bowiem dostęp do wszystkich baz danych zarządzanych przez SGBD. Właściwość url pozwala nam wskazać tę, z której będziemy korzystać.
user, password
Dostęp do baz danych jest chroniony. SGBD obsługuje użytkowników identyfikowanych za pomocą loginu i hasła. Są one określone w atrybutach „user” i „password” tego tagu.
minCount, maxCount
Struts będzie zarządzał pulą połączeń z serwerem SGBD. Otwarcie połączenia z serwerem SGBD jest operacją wymagającą znacznych zasobów czasu i pamięci. Zamiast otwierać i zamykać połączenie z SGBD dla każdego żądania kierowanego do aplikacji, aplikacja zarządza pulą n połączeń, gdzie n mieści się w przedziale [minCount, maxCount]. Jeśli podczas żądania aplikacja potrzebuje połączenia:
- spróbuje pobrać je z puli połączeń. Jeśli znajdzie wolne połączenie, wykorzystuje je.
- jeśli w puli nie ma wolnych połączeń, a liczba połączeń w puli jest mniejsza niż maxCount, otwierane jest nowe połączenie i umieszczane w puli. Będzie je mogło wykorzystać bieżące żądanie.
- jeśli nie ma wolnych połączeń i nie ma możliwości utworzenia nowego, zapytanie zostaje zawieszone.
Gdy bieżące żądanie zamyka połączenie, nie jest ono faktycznie zamykane, lecz zwracane do puli.

W tym przypadku mamy następujące właściwości:

key
dbarticles – jest to nazwa, pod którą źródło danych będzie rozpoznawane w kontekście aplikacji
driverClass
com.mysql.jdbc.Driver. Korzystamy z bazy danych o nazwie MySQL.
url
jdbc:mysql://localhost/dbarticles. Baza danych nosi nazwę dbarticles i znajduje się na komputerze lokalnym.
user, password
admarticles, mdparticles. Użytkownik ten został skonfigurowany z pełnymi uprawnieniami do bazy dbarticles.
minCount, maxCount
2, 5. Minimum 2 połączeń w puli, maksymalnie 5.

9.2.2. Akcje

W pliku konfiguracyjnym Struts zadeklarowano następujące akcje:

    <action-mappings>
        <action path="/liste" type="istia.st.struts.articles.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/listarticles.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
    </action-mappings>

Jedyna akcja nosi nazwę /liste i jest powiązana z klasą istia.st.struts.articles.ListeArticlesAction. Akcja ta kończy się wyświetleniem:

  • strony z artykułami (/vues/listarticles.jsp)
  • strony błędów (/vues/erreurs.jsp)

9.2.3. Plik komunikatów

    <message-resources parameter="istia.st.struts.articles.ApplicationResources" 
        null="false" />

Plik komunikatów ApplicationResources.properties zostanie umieszczony w folderze WEB-INF/classes/istia/st/struts/articles. Jego zawartość będzie następująca:

# błędy
errors.header=<ul>
errors.footer=</ul>
erreur.dbarticles=<li>Erreur d'accès à la base des articles ({0})</li>

Może wystąpić tylko jeden błąd – błąd dostępu do bazy danych. W rzeczywistości istnieje kilka możliwych rodzajów błędów, które są jednak ujęte w tym samym komunikacie o błędzie. Dokładna przyczyna błędu zostanie jednak określona w parametrze {0}.

9.3. Widoki

9.3.1. Widok erreurs.jsp

Ten widok powinien wyświetlać listę błędów. Spotkaliśmy się z nim już kilkakrotnie.

<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>

<html>
    <head>
      <title>Liste d'articles - erreurs</title>
  </head>
  <body>
      <h2>Les erreurs suivantes se sont produites</h2>
        <html:errors/>
  </body>
</html>

Ten widok wyświetla jedynie listę błędów za pomocą tagu <html:errors/>.

9.3.2. Widok listarticles.jsp

Ten widok ma wyświetlać zawartość tabeli ARTICLES z bazy danych. Jego kod wygląda następująco:

<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>

<%
     // listarticles: ArrayList w zapytaniu
     // listArticles(i): tablica (String[5]) zawierająca 5 elementów 
%>

<html>
    <head>
        <title>DataSource Struts</title>
    </head>
    <body>
        <h3>Liste des articles</h3>
        <hr>
        <table border="1">
            <logic:iterate id="ligne" name="listArticles">
                <tr>
                    <logic:iterate id="colonne" name="ligne">
                        <td><bean:write name="colonne"/></td>
                    </logic:iterate>
                </tr>
            </logic:iterate>
        </table>
    </body>
</html>    

Akcja /liste umieści zawartość tabeli ARTICLES w obiekcie ArrayList, umieszczonym w zapytaniu pod nazwą listArticles. Każdy element obiektu listArticles stanowi tablicę składającą się z 5 ciągów znaków, reprezentujących 5 informacji (kod, nazwa, cena, stockActuel, stockMinimum) związanych z pozycją w tabeli. Widok musi wyświetlać zawartość obiektu listArticles w tabeli HTML. W tym celu wykorzystuje tag <logic:iterate>:

            <logic:iterate id="ligne" name="listArticles">
                <tr>
                    <logic:iterate id="colonne" name="ligne">
                        <td><bean:write name="colonne"/></td>
                    </logic:iterate>
                </tr>
            </logic:iterate>

Mamy tu do czynienia z dwiema iteracjami. Pierwsza z nich przebiega po elementach obiektu ArrayList listArticles. Bieżący element obiektu listArticles nazywany jest tutaj wierszem. Element wiersz reprezentuje obiekt typu String[5], który jest przeglądany za pomocą drugiej iteracji. Element tej drugiej iteracji nazywany jest kolumną. Reprezentuje on bieżący element tablicy ciągów znaków i jest zatem ciągiem znaków (kod, nazwa, cena, stockActuel, stockMinimum). Jego wartość jest wyświetlana za pomocą tagu <bean:write>.

Widok wykorzystuje tagi z bibliotek struts-logic i struts-bean. Należy zatem zadeklarować ich użycie:

<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>

9.4. Akcja /liste

Akcja /liste ma na celu umieszczenie w żądaniu obiektu ArrayList reprezentującego zawartość tabeli ARTICLES, aby widok mógł z niego skorzystać. Jej kod wygląda następująco:

package istia.st.struts.articles;

import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionError;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

public class ListeArticlesAction extends Action {

    public ActionForward execute(
        ActionMapping mapping,
        ActionForm form,
        HttpServletRequest request,
        HttpServletResponse response)
        throws IOException, ServletException {

         // odczytuje zawartość tabeli artykułów z połączenia
         // nawiązanej podczas inicjalizacji kontekstu
         // pobierane jest źródło danych dbarticles
        DataSource dataSource = this.getDataSource(request, "dbarticles");
        if (dataSource == null) {
             // nie udało się utworzyć źródła danych
            ActionErrors erreurs = new ActionErrors();
            erreurs.add(    "dbarticles",new ActionError("erreur.dbarticles","La source de données n'a pu être créée"));
            this.saveErrors(request, erreurs);
            return mapping.findForward("afficherErreurs");
        }
         // tutaj źródło danych istnieje  wykorzystujemy         je Connection connection = null;
        Statement st = null;
        ResultSet rs = null;
        String requête = null;
        ArrayList alArticles = new ArrayList();
         // obsługujemy błędy
        try {
             // uzyskiwanie połączenia
            connexion = dataSource.getConnection();
             // przygotowanie zapytania SQL
            requête =
                "select code, nom, prix, stockActuel, stockMinimum from articles order by nom";
             // wykonanie zapytania
            st = connexion.createStatement();
            rs = st.executeQuery(requête);
             // przetwarzanie wyników
            while (rs.next()) {
                 // zapisać bieżący wiersz
                alArticles.add(
                    new String[] {
                        rs.getString("code"),
                        rs.getString("nom"),
                        rs.getString("prix"),
                        rs.getString("stockactuel"),
                        rs.getString("stockMinimum")});
                // następny wiersz
            } //while
             // zwolnij zasoby
            rs.close();
            st.close();
            connexion.close();
        } catch (Exception ex) {
             // wystąpiły błędy
            ActionErrors erreurs = new ActionErrors();
            erreurs.add("dbarticles",new ActionError("erreur.dbarticles", ex.getMessage()));
            this.saveErrors(request, erreurs);
            return mapping.findForward("afficherErreurs");
        }
         // w porządku
        request.setAttribute("listArticles", alArticles);
        return mapping.findForward("afficherListeArticles");
    } //wykonaj
} //klasa

Czytelnik z pewnością jest w stanie zrozumieć istotę powyższego kodu. Skupimy się jedynie na jedynej nowości, jaką jest wykorzystanie obiektu DataSource dostarczanego przez framework Struts. Obiekt ten reprezentuje pulę połączeń skonfigurowaną w sekcji <data-source key="dbarticles"> pliku konfiguracyjnego. Obiekt DataSource został umieszczony, po utworzeniu, w kontekście aplikacji, aby wszystkie obiekty w tym kontekście miały do niego dostęp. Jest on zatem pobierany w następujący sposób:

         // pobieramy źródło danych dbarticles
        DataSource dataSource = this.getDataSource(request, "dbarticles");

W tym miejscu żądamy źródła danych oznaczonego kluczem „dbarticles”. Jeśli otrzymujemy wskaźnik null, oznacza to, że źródło danych nie mogło zostać utworzone podczas inicjalizacji aplikacji. W takim przypadku odnotowujemy błąd w obiekcie ActionErrors i przekazujemy kontrolę do strony błędów oznaczonej kluczem „afficherErreurs” w pliku konfiguracyjnym:

        <action path="/liste" type="istia.st.struts.articles.ListeArticlesAction">
            <forward name="afficherListeArticles" path="/vues/listarticles.jsp"/>
            <forward name="afficherErreurs" path="/vues/erreurs.jsp"/>
        </action>
    </action-mappings>

Po uzyskaniu źródła danych uzyskujemy dostęp do puli połączeń. Połączenie z bazą danych nawiązuje się za pomocą kodu:

             // nawiązywanie połączenia
            Connection connexion = dataSource.getConnection();

W tym przypadku pula połączeń zwróci nam połączenie z puli lub utworzy nowe, jeśli nadal istnieje możliwość jego utworzenia.

9.5. Wdrożenie

Kontekst aplikacji jest zdefiniowany w pliku konfiguracyjnym server.xml serwera Tomcat:

<Context path="/listarticles" reloadable="true" docBase="E:\data\serge\web\struts\articles\liste" />

Struktura drzewa aplikacji wygląda następująco:

  
 
 

Warto zwrócić uwagę na bibliotekę mysql-connector-java-3.0.10-stable-bin.jar znajdującą się w katalogu WEB-INF/lib. To właśnie ona zawiera sterownik JDBC dla bazy MySQL, z której tutaj korzystamy.

9.6. Testy

Uruchamiamy Tomcat i wywołujemy plik URL http://localhost:8080/listarticles/liste.do:

Image

9.7. Drugie źródło danych

Korzystamy teraz z drugiego źródła danych znajdującego się w bazie Postgres. Również w tym przypadku dane znajdują się w tabeli o nazwie ARTICLES, zawierającej te same kolumny co poprzednio. Jej zawartość jest następująca:

Image

To nowe źródło danych jest zadeklarowane w pliku konfiguracyjnym Struts:

    <data-sources>
        <data-source key="dbarticles" >
            <set-property property="driverClass" value="com.mysql.jdbc.Driver"></set-property>
            <set-property property="url" value="jdbc:mysql://localhost/dbarticles"></set-property>
            <set-property property="user" value="admarticles"></set-property>
            <set-property property="password" value="mdparticles"></set-property>
            <set-property property="minCount" value="2"></set-property>
            <set-property property="maxCount" value="5"></set-property>
        </data-source>
        <data-source key="pgdbarticles" type="org.apache.commons.dbcp.BasicDataSource">
            <set-property property="driverClassName" value="org.postgresql.Driver" />
            <set-property property="url" value="jdbc:postgresql://localhost/dbarticles" />
            <set-property property="username" value="serge" />
            <set-property property="password" value="serge" />
            <set-property property="maxActive" value="10" />
            <set-property property="maxWait" value="5000" />
            <set-property property="defaultAutoCommit" value="false" />
            <set-property property="defaultReadOnly" value="false" />
        </data-source>
    </data-sources>

Nowe źródło będzie miało identyfikator (key) pgdbarticles. Nowością jest klasa implementująca interfejs javax.sql.DataSource. Klasą domyślną jest org.apache.struts.util.GenericDataSource zdefiniowana w bibliotece struts.jar. Klasa ta zostanie wycofana w przyszłych wersjach Struts, dlatego w wersji 1.1 zaleca się korzystanie z klasy org.apache.commons.dbcp.BasicDataSource, którą można znaleźć w bibliotece commons-dbcp-1.1.jar. Nie jest ona koniecznie dostarczana wraz z pakietem Struts. Można ją znaleźć pod adresem http://jakarta.apache.org/commons/index.html, a dokładniej pod adresem http://jakarta.apache.org/site/binindex.cgi. Należy pobrać produkt o nazwie Commons DBCP. W systemie Windows można pobrać plik .zip. Zawiera on kod źródłowy biblioteki oraz odpowiadający jej plik .jar. Należy go wyodrębnić z archiwum .zip i umieścić w folderze WEB-INF/lib aplikacji. W tym samym folderze należy również umieścić sterownik używanego modułu SGBD:

Image

Aby skorzystać z tego kodu źródłowego, zmieniamy jedną instrukcję w kodzie akcji /liste:

        DataSource dataSource = this.getDataSource(request, "pgdbarticles");

Tym razem jako źródło danych o nazwie pgdbarticles podajemy plik c.a.d – źródło danych Postgres.

Pozostaje nam tylko skompilować całość, uruchomić Tomcat i wywołać stronę http://localhost:8080/listarticles/liste.do:

Image

9.8. Wnioski

Pokazaliśmy, jak korzystać z puli połączeń w celu uzyskania dostępu do bazy danych. Należy tutaj zauważyć, że nie zastosowaliśmy architektury MVC. W rzeczywistości akcja /liste sama wysyła zapytania SQL w celu uzyskania dostępu do danych. Lepiej byłoby, gdyby zwracała się do pośredniej klasy biznesowej, która ukrywałaby fakt, że dane są pobierane z SGBD. Prawdopodobnie to właśnie ta klasa biznesowa utworzyłaby wówczas pulę połączeń i nie byłoby wtedy potrzeby deklarowania jej w pliku konfiguracyjnym Struts. Jest to pewna wskazówka: obecność sekcji <data-sources> w pliku konfiguracyjnym może oznaczać, że nasza aplikacja nie jest zgodna z architekturą MVC.