Skip to content

9. Sfruttamento di una fonte di dati

In questa sede intendiamo gettare le basi per l’utilizzo dei database all’interno di un’applicazione Struts.

9.1. L'applicazione Struts /listarticles

Vogliamo visualizzare il contenuto di una tabella che registra le caratteristiche degli articoli venduti da un negozio.

code
codice articolo - chiave primaria
nom
nome dell'articolo
prix
prezzo dell'articolo
stockActuel
disponibilità attuale dell'articolo
stockMinimum
scorte al di sotto delle quali
è necessario avviare un rifornimento

Il contenuto della tabella è il seguente:

Image

L'applicazione listarticles ci fornirà lo stesso risultato (in modo meno accurato) in una pagina web:

Image

9.2. Configurazione dell'applicazione Struts /listarticles

Il file web.xml dell'applicazione è standard:

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>

Il file struts-config.xml introduce una nuova sezione <data-sources> che consente di dichiarare e configurare le fonti di dati:

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. Fonti di dati

La sezione <data-sources> consente di dichiarare tutte le fonti di dati dell'applicazione, ciascuna delle quali è descritta da una sezione <data-source>. Questo tag ammette diversi attributi:

key
identifica la fonte di dati quando ce ne sono più di una.
driverClass
il nome della classe da istanziare per accedere al database. Questa classe si trova generalmente in una libreria di classi (.jar) fornita dall'editore di SGBD. Questa libreria contiene il driver JDBC per l'accesso al database. La proprietà driverClass identifica questo driver. Il file .jar contenente le classi di accesso a SGBD verrà collocato nella cartella WEB-INF/lib dell’applicazione.
url
stringa di connessione a un database specifico. Infatti, il driver JDBC consente l’accesso a tutti i database gestiti da SGBD. La proprietà url ci permette di specificare quale database intendiamo utilizzare.
user, password
L’accesso ai database è protetto. Il SGBD gestisce utenti identificati tramite login e password. Questi sono specificati negli attributi user e password del tag.
minCount, maxCount
Struts gestirà un pool di connessioni al SGBD. L’apertura di una connessione a un SGBD è un’operazione che richiede molte risorse in termini di tempo e memoria. Anziché aprire e chiudere una connessione a SGBD per ciascuna delle richieste che verranno effettuate all’applicazione, l’applicazione gestisce un pool di n connessioni, dove n rientra nell’intervallo [minCount, maxCount]. Se durante una richiesta l’applicazione necessita di una connessione:
- cercherà di ottenerne una dal pool di connessioni. Se ne trova una libera, la utilizza.
- se non ci sono connessioni libere nel pool e il numero di connessioni presenti nel pool è inferiore a maxCount, viene aperta una nuova connessione e inserita nel pool. La richiesta corrente potrà utilizzarla.
- se non ci sono connessioni libere e non è possibile crearne una nuova, la richiesta viene messa in attesa.
Quando la richiesta corrente chiude la connessione, questa non viene effettivamente chiusa, ma restituita al pool.

In questo caso, abbiamo le seguenti proprietà:

key
dbarticles: è il nome con cui sarà identificata la fonte di dati nel contesto dell’applicazione
driverClass
com.mysql.jdbc.Driver. Utilizziamo un database MySQL.
url
jdbc:mysql://localhost/dbarticles. Il database si chiama dbarticles ed è presente sul computer locale.
user, password
admarticles, mdparticles. A questo utente sono stati assegnati tutti i privilegi sul database dbarticles.
minCount, maxCount
2, 5. Minimo 2 connessioni nel pool, massimo 5.

9.2.2. Azioni

Le azioni dichiarate nel file di configurazione di Struts sono le seguenti:

    <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>

L'unica azione si chiama /liste ed è associata alla classe istia.st.struts.articles.ListeArticlesAction. Questa azione termina con la visualizzazione:

  • della pagina degli articoli (/vues/listarticles.jsp)
  • della pagina degli errori (/vues/erreurs.jsp)

9.2.3. Il file dei messaggi

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

Il file dei messaggi ApplicationResources.properties verrà inserito nella cartella WEB-INF/classes/istia/st/struts/articles. Il suo contenuto sarà il seguente:

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

Può verificarsi un solo errore, ovvero un errore di accesso al database. Esistono infatti diversi tipi di errori possibili, tutti raggruppati nello stesso messaggio di errore. La causa esatta dell'errore sarà comunque specificata nel parametro {0}.

9.3. Le viste

9.3.1. La vista erreurs.jsp

Questa vista dovrebbe visualizzare un elenco di errori. L’abbiamo già incontrata diverse volte.

<%@ 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>

Questa vista si limita a visualizzare l'elenco degli errori con il tag <html:errors/>.

9.3.2. La vista listarticles.jsp

Questa vista deve visualizzare il contenuto della tabella ARTICLES del database. Il suo codice è il seguente:

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

<%
     // listarticles: ArrayList nella query
     // listArticles(i): array (String[5]) di 5 elementi 
%>

<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>    

L'azione /liste inserirà il contenuto della tabella ARTICLES in un oggetto ArrayList inserito nella query con il nome listArticles. Ogni elemento dell'oggetto listArticles è un array di 5 stringhe di caratteri che rappresentano le 5 informazioni (codice, nome, prezzo, stockActuel, stockMinimum) relative a un articolo presente nella tabella. La vista deve visualizzare il contenuto dell’oggetto listArticles in una tabella HTML. A tal fine utilizza il 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>

Qui ci sono due iterazioni. La prima esegue un ciclo sugli elementi dell'oggetto ArrayList listArticles. L'elemento corrente di listArticles è qui denominato «riga». L'elemento «riga» rappresenta un oggetto String[5] che viene percorso tramite la seconda iterazione. L'elemento di questa seconda iterazione è denominato «colonna». Rappresenta l'elemento corrente di un array di stringhe ed è quindi una stringa (codice, nome, prezzo, stockActuel, stockMinimum). Il suo valore viene visualizzato tramite il tag <bean:write>.

La vista utilizza tag delle librerie struts-logic e struts-bean. È quindi necessario dichiararne l’utilizzo:

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

9.4. L'azione /liste

L'azione /liste ha lo scopo di inserire nella richiesta un oggetto ArrayList che rappresenta il contenuto della tabella ARTICLES, affinché una vista possa utilizzarlo. Il suo codice è il seguente:

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 {

         // legge il contenuto della tabella articoli di una connessione
         // effettuata all'inizializzazione del contesto
         // si recupera la fonte dati dbarticles
        DataSource dataSource = this.getDataSource(request, "dbarticles");
        if (dataSource == null) {
             // non è stato possibile creare la fonte dati
            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");
        }
         // in questo caso la fonte dati esiste: la si ut        ilizza Connection connessione = null;
        Statement st = null;
        ResultSet rs = null;
        String requête = null;
        ArrayList alArticles = new ArrayList();
         // gestiamo gli errori
        try {
             // ottenere una connessione
            connexion = dataSource.getConnection();
             // preparare la query SQL
            requête =
                "select code, nom, prix, stockActuel, stockMinimum from articles order by nom";
             // eseguirla
            st = connexion.createStatement();
            rs = st.executeQuery(requête);
             // elaborare i risultati
            while (rs.next()) {
                 // salvare la riga corrente
                alArticles.add(
                    new String[] {
                        rs.getString("code"),
                        rs.getString("nom"),
                        rs.getString("prix"),
                        rs.getString("stockactuel"),
                        rs.getString("stockMinimum")});
                // riga successiva
            } //while
             // liberare le risorse
            rs.close();
            st.close();
            connexion.close();
        } catch (Exception ex) {
             // si sono verificati degli errori
            ActionErrors erreurs = new ActionErrors();
            erreurs.add("dbarticles",new ActionError("erreur.dbarticles", ex.getMessage()));
            this.saveErrors(request, erreurs);
            return mapping.findForward("afficherErreurs");
        }
         // ok
        request.setAttribute("listArticles", alArticles);
        return mapping.findForward("afficherListeArticles");
    } //esegui
} //classe

Il lettore è senza dubbio in grado di cogliere l'essenza del codice sopra riportato. Ci soffermeremo solo sull'unica novità, ovvero l'utilizzo di un oggetto DataSource fornito dal framework Struts. Questo oggetto DataSource rappresenta il pool di connessioni configurato dalla sezione <data-source key="dbarticles"> del file di configurazione. L’oggetto DataSource, una volta creato, è stato inserito nel contesto dell’applicazione in modo che tutti gli oggetti di tale contesto possano accedervi. Viene quindi recuperato nel modo seguente:

         // si sta recuperando la fonte dati dbarticles
        DataSource dataSource = this.getDataSource(request, "dbarticles");

Qui richiediamo la fonte dati identificata dalla chiave "dbarticles". Se recuperiamo un puntatore null, significa che la fonte dati non è stata creata durante l'inizializzazione dell'applicazione. In questo caso, registriamo l’errore in un oggetto ActionErrors e passiamo il controllo alla pagina degli errori identificata dalla chiave "afficherErreurs" nel file di configurazione:

        <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>

Una volta ottenuta la fonte dei dati, abbiamo accesso al pool delle connessioni. Una connessione al database viene stabilita tramite il codice:

             // ottenere una connessione
            Connection connexion = dataSource.getConnection();

In questo caso, il pool ci fornirà una connessione riciclata o ne creerà una nuova, qualora fosse ancora possibile crearne una.

9.5. Distribuzione

Il contesto dell’applicazione è definito nel file di configurazione server.xml di Tomcat:

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

La struttura ad albero dell’applicazione è la seguente:

  
 
 

Si noti la libreria mysql-connector-java-3.0.10-stable-bin.jar in WEB-INF/lib. È questa che contiene il driver JDBC della base MySQL qui utilizzata.

9.6. I test

Avviamo Tomcat e richiediamo l'http://localhost:8080/listarticles/liste.doe URL:

Image

9.7. Una seconda fonte di dati

Ora utilizziamo una seconda fonte di dati presente in un database Postgres. Anche in questo caso, i dati si trovano in una tabella ARTICLES con le stesse colonne di prima. Il suo contenuto è il seguente:

Image

Questa nuova fonte di dati è dichiarata nel file di configurazione di 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>

La nuova fonte avrà come identificatore (key) pgdbarticles. La novità deriva dalla classe che implementa l'interfaccia javax.sql.DataSource. La classe predefinita è org.apache.struts.util.GenericDataSource, definita nella libreria struts.jar. Questa classe verrà abbandonata nelle future versioni di Struts e, per la versione 1.1, si consiglia di utilizzare la classe org.apache.commons.dbcp.BasicDataSource, che si trova nella libreria commons-dbcp-1.1.jar. Quest’ultima non è necessariamente inclusa nel pacchetto Struts. È possibile trovarla all'URL http://jakarta.apache.org/commons/index.html e, più precisamente, all'URL http://jakarta.apache.org/site/binindex.cgi. È necessario scaricare il prodotto denominato Commons DBCP. Per Windows è possibile scaricare il file .zip. Questo contiene il codice sorgente della libreria e il corrispondente file .jar. È necessario estrarre quest'ultimo dall'archivio .zip e inserirlo nella cartella WEB-INF/lib dell'applicazione. È inoltre necessario inserire nella stessa cartella il driver del SGBD utilizzato:

Image

Per utilizzare questo codice sorgente, modifichiamo un’istruzione nel codice dell’azione /liste:

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

Questa volta specifichiamo la fonte dati denominata pgdbarticles, c.a.d, ovvero la fonte dati Postgres.

Non ci resta che compilare il tutto, avviare Tomcat e richiedere l'http://localhost:8080/listarticles/liste.do:

Image

9.8. Conclusione

Abbiamo mostrato come utilizzare un pool di connessioni per accedere a un database. Notiamo qui che non abbiamo rispettato l’architettura MVC. Infatti, l’azione /liste esegue direttamente le query SQL per accedere ai dati. Sarebbe stato preferibile che si rivolgesse a una classe di business intermedia che nascondesse il fatto che i dati vengono recuperati da un SGBD. Probabilmente sarebbe stata proprio la classe di business a creare il pool di connessioni e non sarebbe stato quindi necessario dichiararlo nel file di configurazione di Struts. Questo è un indizio: la presenza di una sezione <data-sources> nel file di configurazione può significare che la nostra applicazione non rispetta l’architettura MVC.