Skip to content

2. Teil 2

2.1. Introduction

Zunächst möchten wir noch einmal zusammenfassen, was in Teil 1 behandelt wurde, insbesondere die verwendete dreischichtige Architektur [web, domain, dao]. In der vorgeschlagenen Lösung war die Schicht [dao] eine Testschicht: Die Datenquelle wurde durch ein Objekt [ArrayList] implementiert. In diesem Artikel konzentrieren wir uns auf die Schicht [dao] und stellen verschiedene mögliche Implementierungen dieser Schicht vor, wenn sich die Daten in einem SGBD befinden.

Verwendete Tools:

  • das Firebird-Objekt SGBD – siehe Anhang, Abschnitt 3.5.
  • SGBD MSDE (Microsoft Data Engine) – siehe Anhang, Abschnitt 3.12.
  • IBExpert, Personal Edition zur grafischen Verwaltung von SGBD Firebird – siehe Anhang, Abschnitt 3.6.
  • EMS MS SQL Manager zur grafischen Verwaltung von SGBD MSDE – siehe Anhang, Abschnitt 3.14.
  • Ibatis SqlMap für die Datenzugriffsebene des SGBD – siehe Abschnitt 2.5.6.2.

Auf einer Skala von Anfänger über Mittelstufe bis Fortgeschrittener befindet sich dieses Dokument im Bereich [intermédiaire-avancé]. Zum Verständnis sind verschiedene Vorkenntnisse erforderlich. Einige davon können in Dokumenten erworben werden, die ich verfasst habe. In diesem Fall verweise ich darauf. Es versteht sich von selbst, dass dies nur ein Vorschlag ist und der Leser seine bevorzugten Dokumente verwenden kann.

  • Sprache VB.net: [Introduction au langage VB.NET par l'exemple ]
  • Webprogrammierung in VB.net: [Développement WEB avec ASP.NET 1.1 ]
  • Verwendung des Spring-Aspekts IoC: [Spring IoC pour .NET ]
  • Ibatis-Dokumentation SqlMap: [http://prdownloads.sourceforge.net/ibatisnet/DevGuide.pdf?download]
  • Firebird-Dokumentation: [http://firebird.sourceforge.net/pdfmanual/Firebird-1.5-QuickStart.pdf]
  • Dokumentation zu Spring.net: [http://www.springframework.net/documentation.html]

2.2. Die Webanwendung „articles“ – Zusammenfassung

Hier stellen wir die Elemente der in Teil 1 behandelten vereinfachten E-Commerce-Webanwendung vor. Diese ermöglicht es Webkunden:

  • eine Liste von Artikeln aus einer Datenbank einzusehen
  • bestimmte Artikel in einen elektronischen Warenkorb zu legen
  • diesen zu bestätigen. Diese Bestätigung bewirkt lediglich, dass die Lagerbestände der gekauften Artikel in der Datenbank aktualisiert werden.

2.2.1. Die Ansichten der Anwendung

Dem Benutzer werden folgende Ansichten angezeigt:

  • die Ansicht [ERREURS], die alle Fehler der Anwendung anzeigt

Image

2.2.2. Allgemeine Architektur der Anwendung

Die in Teil 1 erstellte Anwendung basiert auf einer dreischichtigen Architektur:

  • Die drei Schichten wurden durch die Verwendung von Schnittstellen voneinander unabhängig gemacht
  • Die Integration der verschiedenen Schichten erfolgte mit Spring
  • Jede Schicht verfügt über separate Namespaces: Web (Schicht UI), Domain (Geschäftslogik-Schicht) und DAO (Datenzugriffsschicht).

Die Anwendung folgt einer MVC-Architektur (Model-View-Controller). Wenn wir das obige Schichtschema heranziehen, fügt sich die MVC-Architektur wie folgt ein:

Die Bearbeitung einer Anfrage eines Kunden erfolgt in folgenden Schritten:

  1. Der Kunde sendet eine Anfrage an den Controller. Dieser Controller ist hier eine .aspx-Seite, der eine besondere Rolle zukommt. Sie empfängt alle Anfragen der Kunden. Sie ist das Eingangstor der Anwendung. Sie entspricht dem C in MVC.
  2. Der Controller verarbeitet diese Anfrage. Dazu benötigt er möglicherweise die Unterstützung der Geschäftslogikschicht, die in der Struktur MVC als Modell M bezeichnet wird.
  3. Der Controller erhält eine Antwort von der Geschäftslogikschicht. Die Anfrage des Kunden wurde bearbeitet. Dies kann zu mehreren möglichen Antworten führen. Ein klassisches Beispiel ist
    • eine Fehlerseite, falls die Anfrage nicht korrekt verarbeitet werden konnte
    • ansonsten eine Bestätigungsseite
  4. Der Controller wählt die Antwort (= Ansicht) aus, die an den Client gesendet werden soll. Dabei handelt es sich meist um eine Seite mit dynamischen Elementen. Der Controller stellt diese der Ansicht zur Verfügung.
  5. Die Ansicht wird an den Client gesendet. Dies ist das „V“ in MVC.

2.2.3. Das Modell

Das Modell M von MVC besteht hier aus folgenden Elementen:

  1. die Geschäftsklassen
  2. die Datenzugriffsklassen
  3. die Datenbank

2.2.3.1. Die Datenbank

Die Datenbank enthält nur eine Tabelle namens ARTICLES, die mit den folgenden Befehlen SQL generiert wurde:

CREATE TABLE ARTICLES (
    ID            INTEGER NOT NULL,
    NOM           VARCHAR(20) NOT NULL,
    PRIX          NUMERIC(15,2) NOT NULL,
    STOCKACTUEL   INTEGER NOT NULL,
    STOCKMINIMUM  INTEGER NOT NULL
);
/* Einschränkungen */
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_ID check (ID>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRIX check (PRIX>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKACTUEL check (STOCKACTUEL>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKMINIMUM check (STOCKMINIMUM>=0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_NOM check (NOM<>'');
ALTER TABLE ARTICLES ADD CONSTRAINT UNQ_NOM UNIQUE (NOM);
/* Primärschlüssel */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
Primärschlüssel, der einen Artikel eindeutig identifiziert
nom
Name des Artikels
prix
sein Preis
stockactuel
Aktueller Lagerbestand
stockminimum
der Lagerbestand, bei dessen Unterschreitung eine Nachbestellung erfolgen muss

2.2.3.2. Die Namensräume des Modells

Das Modell M wird in Form von zwei Namensräumen bereitgestellt:

  • istia.st.articles.dao: enthält die Klassen für den Datenzugriff der Schicht [dao]
  • istia.st.articles.domain: enthält die Fachklassen der Schicht [domain]

Jeder dieser Namespaces ist in einer eigenen „Assembly“-Datei enthalten:

assembly
contenu
rôle
webarticles-dao
- [IArticlesDao]: Die Schnittstelle für den Zugriff auf die Schicht [dao]. Dies ist die einzige Schnittstelle, die die Schicht [domain] sieht. Sie sieht keine andere.
- [Article]: Klasse zur Definition eines Artikels
- [ArticlesDaoArrayList]: Implementierungsklasse der Schnittstelle [IArticlesDao] mit einer Klasse [ArrayList]
Datenzugriffsebene – befindet sich vollständig in der Schicht [dao] der 3-Tier-Architektur der Webanwendung
webarticles-domain
- [IArticlesDomain]: Die Schnittstelle für den Zugriff auf die Schicht [domain]. Dies ist die einzige Schnittstelle, die die Webschicht sieht. Sie sieht keine andere.
- [AchatsArticles]: eine Klasse, die [IArticlesDomain] implementiert
- [Achat]: Klasse, die den Einkauf eines Kunden repräsentiert
- [Panier]: Klasse, die die Gesamtheit der Einkäufe eines Kunden darstellt
stellt das Modell der Online-Einkäufe dar – befindet sich vollständig in der Schicht [domain] der 3-Tier-Architektur der Webanwendung

2.2.4. Bereitstellung und Tests der Anwendung [webarticles]

2.2.4.1. Bereitstellung

Wir stellen die in Teil 1 des Artikels entwickelte Anwendung in einem Ordner namens [runtime] bereit:

 

Anmerkungen:

Der Ordner „[runtime]“ enthält drei Dateien und zwei Unterordner:

  • die Controller [global.asax] und [main.aspx]
  • die Konfigurationsdatei [web.config]
  • den Ordner [bin], der Folgendes enthält:
    • die DLL-Dateien der drei Schichten [webarticles-dao.dll], [webarticles-domain.dll] und [webarticles-web.dll]
    • die für Spring erforderlichen Dateien [Spring-Core.*], [log4net.dll]
  • den Ordner [vues], der den Code für die Darstellung der verschiedenen Ansichten enthält.
  • Die .vb-Code-Dateien sind nicht erforderlich, da sich ihre kompilierte Version in den Dateien DLL befindet.

2.2.4.2. Tests

Wir konfigurieren den Webserver [Cassini] wie folgt:

Image

mit:

Physikalischer Pfad: D:\data\serge\travail\2004-2005\aspnet\webarticles-010405\runtime\

Virtueller Pfad: /webarticles

Mit einem Browser rufen wir die Seiten URL und [http://localhost/webarticles/main.aspx] auf

Image

Zur Erinnerung: Die Schicht [dao] wird durch eine Klasse implementiert, die die Artikel in einem Objekt [ArrayList] speichert. Diese Klasse erstellt eine anfängliche Liste mit vier Artikeln. Ausgehend von der obigen Ansicht nutzen wir die Menü-Links, um Operationen durchzuführen. Hier sind einige davon. Die linke Spalte stellt die Anfrage des Kunden dar, die rechte Spalte die Antwort darauf.

2.2.5. Die überarbeitete Schicht [dao]

In unserer ersten Implementierung der Schicht [dao] war die Datenzugriffsschnittstelle [IArticlesDao] durch eine Klasse implementiert worden, die die Artikel in einem Objekt [ArrayList] speicherte. Dadurch konnten wir uns nicht näher mit dieser Schicht befassen und zeigen, dass nur ihre Schnittstelle und nicht ihre Implementierung von Bedeutung war. So konnten wir eine funktionsfähige Webanwendung erstellen. Diese verfügt über drei Schichten: [web], [domain] und [dao]. Wir werden hier verschiedene Implementierungen der Schicht [dao] vorstellen. Jede davon kann die aktuelle Schicht [dao] ersetzen, ohne dass Änderungen an den Schichten [domain] und [web] erforderlich sind. Diese Flexibilität ergibt sich daraus, dass:

  • die Schicht [domain] nicht auf eine konkrete Klasse, sondern auf eine Schnittstelle [IArticlesDao] verweist
  • dank Spring konnten wir den Namen der Implementierungsklasse der Schnittstelle [IArticlesDao] vor der Schicht [domain] verbergen.

2.2.5.1. Elemente der Schicht [dao]

Hier noch einmal einige Elemente der Schicht [dao], die in den neuen Implementierungen beibehalten werden:

  • - [IArticlesDao]: Schnittstelle für den Zugriff auf die Schicht [dao]
  • - [Article]: Klasse zur Definition eines Artikels

2.2.5.2. Die Klasse [Article]

Die Klasse, die einen Artikel definiert, lautet wie folgt:

Imports System

Namespace istia.st.articles.dao

    Public Class Article

         ' private Felder
        Private _id As Integer
        Private _nom As String
        Private _prix As Double
        Private _stockactuel As Integer
        Private _stockminimum As Integer

         ' Artikelnummer
        Public Property id() As Integer
            Get
                Return _id
            End Get
            Set(ByVal Value As Integer)
                If Value <= 0 Then
                    Throw New Exception("Le champ id [" + Value.ToString + "] est invalide")
                End If
                Me._id = Value
            End Set
        End Property

         ' Artikelname
        Public Property nom() As String
            Get
                Return _nom
            End Get
            Set(ByVal Value As String)
                If Value Is Nothing OrElse Value.Trim.Equals("") Then
                    Throw New Exception("Le champ nom [" + Value + "] est invalide")
                End If
                Me._nom = Value
            End Set
        End Property

         ' Artikelpreis
        Public Property prix() As Double
            Get
                Return _prix
            End Get
            Set(ByVal Value As Double)
                If Value < 0 Then
                    Throw New Exception("Le champ prix [" + Value.ToString + "] est invalide")
                End If
                Me._prix = Value
            End Set
        End Property

         ' aktueller Lagerbestand des Artikels
        Public Property stockactuel() As Integer
            Get
                Return _stockactuel
            End Get
            Set(ByVal Value As Integer)
                If Value < 0 Then
                    Throw New Exception("Le champ stockActuel [" + Value.ToString + "] est invalide")
                End If
                Me._stockactuel = Value
            End Set
        End Property

         ' Mindestbestand des Artikels
        Public Property stockminimum() As Integer
            Get
                Return _stockminimum
            End Get
            Set(ByVal Value As Integer)
                If Value < 0 Then
                    Throw New Exception("Le champ stockMinimum [" + Value.ToString + "] est invalide")
                End If
                Me._stockminimum = Value
            End Set
        End Property

         ' Standardhersteller
        Public Sub New()
        End Sub

         ' Hersteller mit Eigenschaften
        Public Sub New(ByVal id As Integer, ByVal nom As String, ByVal prix As Double, ByVal stockactuel As Integer, ByVal stockminimum As Integer)
            Me.id = id
            Me.nom = nom
            Me.prix = prix
            Me.stockactuel = stockactuel
            Me.stockminimum = stockminimum
        End Sub

         ' Artikelidentifikationsmethode
        Public Overrides Function ToString() As String
            Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
        End Function
    End Class
End Namespace

Diese Klasse bietet:

  1. einen Konstruktor, mit dem die 5 Informationen eines Artikels festgelegt werden können: [id, nom, prix, stockactuel, stockminimum]
  2. öffentliche Eigenschaften zum Lesen und Schreiben der 5 Informationen.
  3. eine Überprüfung der in den Artikel eingegebenen Daten. Bei fehlerhaften Daten wird eine Ausnahme ausgelöst.
  4. Eine Methode toString, mit der der Wert eines Artikels als Zeichenkette abgerufen werden kann. Dies ist häufig für die Fehlersuche in einer Anwendung nützlich.

2.2.5.3. Die Schnittstelle [IArticlesDao]

Die Schnittstelle [IArticlesDao] ist wie folgt definiert:

Imports System
Imports System.Collections

Namespace istia.st.articles.dao

    Public Interface IArticlesDao
         ' Liste aller Artikel
        Function getAllArticles() As IList
         ' Artikel hinzufügen
        Function ajouteArticle(ByVal unArticle As Article) As Integer
         ' Artikel löschen
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
         ' Artikel bearbeiten
        Function modifieArticle(ByVal unArticle As Article) As Integer
         ' Artikel suchen
        Function getArticleById(ByVal idArticle As Integer) As Article
         ' Löscht alle Artikel
        Sub clearAllArticles()
         ' ändert den Lagerbestand eines Artikels
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

Die verschiedenen Methoden der Schnittstelle haben folgende Funktionen:

getAllArticles
gibt alle Artikel aus der Datenquelle zurück
clearAllArticles
leert die Datenquelle
getArticleById
gibt das durch seine Nummer identifizierte Objekt [Article] zurück
ajouteArticle
ermöglicht das Hinzufügen eines Artikels zur Datenquelle
modifieArticle
ermöglicht die Änderung eines Artikels in der Datenquelle
supprimerArticle
ermöglicht das Löschen eines Artikels aus der Datenquelle
changerStockArticle
ermöglicht die Änderung des Bestands eines Artikels in der Datenquelle

Die Schnittstelle stellt Client-Programmen eine Reihe von Methoden zur Verfügung, die ausschließlich durch ihre Signaturen definiert sind. Sie kümmert sich nicht darum, wie diese Methoden tatsächlich implementiert werden. Dies sorgt für Flexibilität in einer Anwendung. Das Client-Programm ruft die Schnittstelle auf und nicht eine bestimmte Implementierung davon.

Die Auswahl einer bestimmten Implementierung erfolgt über eine Spring-Konfigurationsdatei.

2.3. Die Implementierungsklasse [ArticlesDaoPlainODBC]

Wir bieten eine neue Implementierung der Schicht [dao] an, die davon ausgeht, dass sich die Daten in einer Quelle ODBC befinden. Es ist bekannt, dass unter Windows fast alle auf dem Markt erhältlichen SGBD über einen Treiber ODBC verfügen. Der Vorteil dieser Lösung besteht darin, dass der SGBD für die Anwendung transparent gewechselt werden kann. Der Nachteil besteht darin, dass ein ODBC-Treiber, der nur die allen SGBD-Geräten gemeinsamen Funktionen nutzt, in der Regel weniger leistungsfähig ist als ein Treiber, der speziell dafür geschrieben wurde, das gesamte Potenzial eines bestimmten SGBD-Geräts auszuschöpfen. In Abschnitt 3.7 finden Sie ein Beispiel für die Erstellung einer ODBC-Quelle.

2.3.1. Der Code

2.3.1.1. Das Grundgerüst

Die Klasse [ArticlesDaoPlainODBC] implementiert die Schnittstelle [IArticlesDao] wie folgt:

Imports System
Imports System.Collections
Imports System.Data.Odbc

Namespace istia.st.articles.dao

    Public Class ArticlesDaoPlainODBC
        Implements istia.st.articles.dao.IArticlesDao

         ' private Felder
        Private connexion As OdbcConnection = Nothing
        Private DSN As String
        Private insertCommand As OdbcCommand
        Private updatecommand As OdbcCommand
        Private deleteSomeCommand As OdbcCommand
        Private selectSomeCommand As OdbcCommand
        Private updateStockCommand As OdbcCommand
        Private deleteAllCommand As OdbcCommand
        Private selectAllCommand As OdbcCommand

         ' Hersteller
        Public Sub New(ByVal DSN As String, ByVal uid As String, ByVal password As String)
             ' DSN: Name der Quelle ODBC
             ' uid: Benutzer-ID
             ' Passwort: sein Passwort
...
        End Sub

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
...
        End Function

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
...
        End Function

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
...
        End Sub

        Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
...
        End Function

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
...
        End Function

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
...
        End Function

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
....
        End Function

        Private Function executeQuery(ByVal query As OdbcCommand) As IList
             ' Ausführung einer Abfrage SELECT 
....
        End Function

        Private Function executeUpdate(ByVal sqlCommand As OdbcCommand) As Integer
....
    End Class

End Namespace

Anmerkungen:

  • In Zeile 3 wird der Namensraum importiert, der die Klassen .NET für den Zugriff auf die Quellen ODBC enthält
  • Zeile 11 – speichert die Verbindung zur Datenquelle ODBC
  • Zeile 12 – Speichert den Namen der Datenquelle DSN
  • Zeilen 13–19 – private Variablen vom Typ [OdbcCommand], die die Abfragen SQL definieren, die von den verschiedenen Methoden der Klasse verwendet werden
  • Zeilen 22–27 – der Konstruktor. Er erhält die Elemente, mit denen er das Objekt [OdbcConnection] erstellen kann, das den Code mit der Datenquelle ODBC verknüpft
  • Zeilen 29–31 – die Methode zum Hinzufügen eines Artikels
  • Zeilen 33–35 – die Methode zum Ändern des Lagerbestands eines Artikels
  • Zeilen 37–39 – die Methode zum Löschen aller Artikel aus der Datenquelle ODBC
  • Zeilen 41–43 – die Methode zum Abrufen der Liste aller Artikel aus der Datenquelle ODBC
  • Zeilen 45–47 – die Methode zum Abrufen eines bestimmten Artikels
  • Zeilen 49–51 – die Methode, mit der bestimmte Felder eines Artikels geändert werden können, dessen Nummer bekannt ist
  • Zeilen 53–55 – die Methode, mit der ein Artikel anhand seiner Nummer gelöscht werden kann
  • Zeilen 57–60 – Hilfsmethode, mit der ein [SELECT] auf die Datenquelle angewendet und das Ergebnis zurückgegeben werden kann
  • Zeilen 62–64 – Hilfsmethode, mit der ein [INSERT, UPDATE, DELETE] auf der Datenquelle ausgeführt und das Ergebnis zurückgegeben werden kann

2.3.1.2. Der Hersteller

         ' Konstruktor
        Public Sub New(ByVal DSN As String, ByVal uid As String, ByVal password As String)
             ' DSN: Name der Quelle ODBC
             ' uid: Benutzer-ID
             ' Passwort: sein Passwort

             ': Der als Argument übergebene Name der Datenbank wird abgerufen
            Me.DSN = DSN
            Dim connectString As String = String.Format("DSN={0};UID={1};PASSWORD={2}", DSN, uid, password)
             ': Die Verbindung wird hergestellt
            connexion = New OdbcConnection(connectString)
             ' Die Abfragen werden vorbereitet SQL
            insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
            updatecommand = New OdbcCommand("update ARTICLES set nom=?, prix=?, stockactuel=?, stockminimum=? where id=?", connexion)
            deleteSomeCommand = New OdbcCommand("delete from ARTICLES where id=?", connexion)
            selectSomeCommand = New OdbcCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=?", connexion)
            updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
            selectAllCommand = New OdbcCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
            deleteAllCommand = New OdbcCommand("delete from ARTICLES", connexion)
        End Sub

Anmerkungen:

  • Zeile 2 – Der Konstruktor erhält die drei Informationen, die er benötigt, um eine Verbindung zu einer ODBC-Quelle herzustellen: den Namen DSN der Quelle, die Anmelde-ID, mit der die Verbindung hergestellt werden soll, sowie das zugehörige Passwort.
  • Zeile 8 – Der Name der Quelle DSN wird gespeichert, um ihn in Fehlermeldungen wiedergeben zu können.
  • Zeile 9 – Das Objekt [OdbcConnection] wird instanziiert. Eine instanziierte Verbindung ist keine geöffnete Verbindung. Das Öffnen erfolgt durch die Methode [open].
  • Zeilen 12–19 – Die Abfragen SQL werden in den Objekten [OdbcCommand] vorbereitet. So müssen wir sie nicht jedes Mal neu erstellen, wenn wir sie benötigen. Die formalen Parameter ? der Abfragen werden bei der Ausführung der Abfrage durch tatsächliche Werte ersetzt.

2.3.1.3. Die Methode executeQuery

        Private Function executeQuery(ByVal query As OdbcCommand) As IList
             ' Ausführung einer Abfrage SELECT 
             ' Deklaration des Objekts, das den Zugriff auf alle Zeilen der Ergebnistabelle ermöglicht
            Dim myReader As OdbcDataReader = Nothing
            Try
                 ': Es wird eine Verbindung zu BDD hergestellt
                connexion.Open()
                 ': Die Abfrage wird ausgeführt
                myReader = query.ExecuteReader()
                 ': Deklaration einer Artikelliste, um diese anschließend zurückzugeben
                Dim articles As IList = New ArrayList
                Dim unArticle As Article
                While myReader.Read()
                     ': Ein Artikel wird mit den Werten des Lesegeräts vorbereitet
                    unArticle = New Article
                    unArticle.id = myReader.GetInt32(0)
                    unArticle.nom = myReader.GetString(1)
                    unArticle.prix = myReader.GetDouble(2)
                    unArticle.stockactuel = myReader.GetInt32(3)
                    unArticle.stockminimum = myReader.GetInt32(4)
                     ': Der Artikel wird zur Liste hinzugefügt
                    articles.Add(unArticle)
                End While
                 ': Das Ergebnis wird zurückgegeben
                Return articles
            Finally
                 ' Freigabe der Ressourcen
                If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function

Anmerkungen:

  • Die Methode [executeQuery] ist eine Hilfsmethode, die:
    • eine Abfrage [SELECT id, nom, prix, stockactuel, stockminimum from ARTICLES ...] auf die Datenquelle ausführt
    • das Ergebnis in Form einer Liste von [Article]-Objekten zurückgibt
  • Zeile 1 – Der einzige Parameter der Methode ist das Objekt [OdbcCommand], das die auszuführende Abfrage [Select] enthält.
  • Zeile 7 – Die Verbindung wird geöffnet. Sie wird in Zeile 29 geschlossen, unabhängig davon, ob ein Fehler aufgetreten ist oder nicht.
  • Zeile 9 – Das Objekt [OdbcDataReader], das zur Auswertung des Ergebnisses von [Select] benötigt wird, wird instanziiert
  • Zeilen 13–23 – Jede Ergebniszeile von [Select] wird in ein Objekt [Article] gespeichert, das zusammen mit den anderen Artikeln in ein Objekt [ArrayList] aufgenommen wird
  • Die Liste der Artikel wird in Zeile 25 ausgegeben
  • Es werden keine Ausnahmen behandelt. Diese müssen vom Code behandelt werden, der diese Methode aufruft.

2.3.1.4. Die Methode executeUpdate

        Private Function executeUpdate(ByVal sqlCommand As OdbcCommand) As Integer
             ' Ausführung einer Aktualisierungsanfrage
            Try
                 ': Es wird eine Verbindung zur BDD hergestellt
                connexion.Open()
                 'Die Abfrage wird ausgeführt
                Return sqlCommand.ExecuteNonQuery()
            Finally
                 ' Freigabe der Ressourcen
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function
    End Class

Anmerkungen:

  • Die Methode erhält ein Objekt [OdbcCommand], das eine Abfrage SQL vom Typ [Insert, Update, Delete] enthält.
  • Die Verbindung wird in Zeile 5 geöffnet. Sie wird in Zeile 10 geschlossen, unabhängig davon, ob eine Ausnahme aufgetreten ist oder nicht.
  • Die Aktualisierungsanfrage wird in Zeile 7 ausgeführt. Das Ergebnis – die Anzahl der durch die Anfrage geänderten Zeilen in der Tabelle ARTICLES – wird sofort zurückgegeben.

2.3.1.5. Die Methode ajouteArticle

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' Exklusivbereich
            SyncLock Me
                 ' Die Einfügeabfrage wird vorbereitet
                With insertCommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("id", unArticle.id))
                    .Add(New OdbcParameter("nom", unArticle.nom))
                    .Add(New OdbcParameter("prix", unArticle.prix))
                    .Add(New OdbcParameter("stockactuel", unArticle.stockactuel))
                    .Add(New OdbcParameter("stockminimum", unArticle.stockminimum))
                End With
                Try
                     'Ausführung der Abfrage
                    Return executeUpdate(insertCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
                End Try
            End SyncLock
        End Function

Kommentare:

  • Zeile 1 – Die Methode erhält den Artikel, der zur Datenquelle ODBC hinzugefügt werden soll. Sie gibt die Anzahl der von diesem Vorgang betroffenen Zeilen zurück, c.a.d. 1 oder 0
  • Zeilen 3 und 20 – Die Methode ist synchronisiert. Dies gilt für alle Methoden, die auf Daten zugreifen. Das bedeutet, dass jeweils nur ein Thread gleichzeitig an der Datenquelle arbeiten kann. Dies ist wahrscheinlich zu konservativ. Es gibt bessere Alternativen, insbesondere die Einbindung dieser Operationen in Transaktionen. In diesem Fall verwaltet das Objekt SGBD die parallelen Zugriffe. Wir wollten das Konzept der Transaktionen zum jetzigen Zeitpunkt noch nicht einführen. Spring bietet uns die Möglichkeit, diese in der Schicht [domain] einzuführen. Vielleicht haben wir in einem anderen Artikel Gelegenheit, darauf zurückzukommen.
  • In den Zeilen 5–12 werden die formalen Parameter der Abfrage des vom Konstruktor initialisierten Objekts [insertCommand] mit Werten belegt. Hier noch einmal die Abfrage:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)

Die fünf für die Abfrage erforderlichen Werte werden in den Zeilen 7–11 bereitgestellt.

  • In den Zeilen 13–19 wird die Abfrage ausgeführt. Verläuft sie erfolgreich, wird das Ergebnis zurückgegeben. Andernfalls wird eine generische Ausnahme mit einer eindeutigen Fehlermeldung ausgelöst

2.3.1.6. Die Methode modifieArticle

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
             ' Exklusivbereich
            SyncLock Me
                 ' Die Update-Abfrage wird vorbereitet
                With updatecommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("nom", unArticle.nom))
                    .Add(New OdbcParameter("prix", unArticle.prix))
                    .Add(New OdbcParameter("stockactuel", unArticle.stockactuel))
                    .Add(New OdbcParameter("stockminimum", unArticle.stockminimum))
                    .Add(New OdbcParameter("id", unArticle.id))
                End With
                 ' die Abfrage wird ausgeführt
                Try
                     'Die Einfügeabfrage wird ausgeführt
                    Return executeUpdate(updatecommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
                End Try
            End SyncLock
        End Function

Kommentare:

  • Zeile 1 – Die Methode erhält den zu ändernden Artikel aus der Datenquelle ODBC. Sie gibt die Anzahl der von diesem Vorgang betroffenen Zeilen zurück, c.a.d. 1 oder 0
  • Die Kommentare zur Methode [ajouteArticle] können hier übernommen werden

2.3.1.7. Die Methode supprimeArticle

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' Exklusivbereich
            SyncLock Me
                 ' Die DELETE-Abfrage wird vorbereitet
                With deleteSomeCommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("id", idArticle))
                End With
                 'wird ausgeführt
                Try
                     'Die Löschanfrage wird ausgeführt
                    Return executeUpdate(deleteSomeCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

Kommentare:

  • Zeile 1 – Die Methode erhält die Nummer des zu löschenden Artikels aus der Datenquelle ODBC. Sie gibt die Anzahl der von diesem Vorgang betroffenen Zeilen zurück, c.a.d. 1 oder 0
  • Die Kommentare zur Methode [ajouteArticle] können hier übernommen werden

2.3.1.8. Die Methode getAllArticles

            Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
             ' Exklusiver Abschnitt
            SyncLock Me
                Try
                     'Die SELECT-Abfrage wird ausgeführt
                    Dim articles As IList = executeQuery(selectAllCommand)
                     'Die Liste wird zurückgegeben
                    Return articles
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
                End Try
            End SyncLock
        End Function

Kommentare:

  • Zeile 1 – Die Methode erhält keine Parameter. Sie gibt die Liste aller Artikel aus der Datenquelle ODBC zurück
  • Die Abfrage [Select], die alle Artikel abfragt, wird in Zeile 6 an die Methode [executeQuery] gesendet
  • Die erhaltene Liste wird in Zeile 8 zurückgegeben
  • In den Zeilen 9–12 wird eine mögliche Ausnahme behandelt

2.3.1.9. Die Methode getArticleById

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Die SELECT-Abfrage wird vorbereitet
                With selectSomeCommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("id", idArticle))
                End With
                 ': Die Abfrage wird ausgeführt
                Try
                     'Die Abfrage wird ausgeführt
                    Dim articles As IList = executeQuery(selectSomeCommand)
                     'Es wird geprüft, ob der Artikel gefunden wurde
                    If articles.Count = 0 Then Return Nothing
                     'Der Artikel wird zurückgegeben
                    Return CType(articles.Item(0), Article)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

Anmerkungen:

  • Zeile 1 – Die Methode erhält als Parameter die Nummer des gewünschten Artikels. Sie gibt diesen zurück, wenn er in der Quelle ODBC gefunden wird, andernfalls gibt sie die Referenz [nothing] zurück.
  • Die Abfrage [Select], die den Artikel anfordert, wird in den Zeilen 5–8 initialisiert
  • sie wird in Zeile 12 ausgeführt – man erhält eine Liste von Artikeln
  • Ist diese Liste leer, wird die Referenz [nothing] in Zeile 14 zurückgegeben
  • Andernfalls wird der einzige Artikel der Liste in Zeile 16 zurückgegeben
  • In den Zeilen 17–20 wird eine mögliche Ausnahme behandelt

2.3.1.10. Die Methode clearAllArticles

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' exklusiver Abschnitt
            SyncLock Me
                Try
                     ': Die Löschanfrage wird ausgeführt
                    executeUpdate(deleteAllCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
                End Try
            End SyncLock
        End Sub

Anmerkungen:

  • Zeile 1 – Die Methode erhält keine Parameter und gibt nichts zurück
  • Zeile 6 – Die Anforderung zum Löschen aller Artikel wird ausgeführt
  • Zeilen 7–10: Eine mögliche Ausnahme wird behandelt

2.3.1.11. Die Methode changerStockArticle

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Aktualisierungsanfrage für den Lagerbestand wird vorbereitet
                With updateStockCommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("mvt1", mouvement))
                    .Add(New OdbcParameter("id", idArticle))
                    .Add(New OdbcParameter("mvt2", mouvement))
                End With
                 'wird ausgeführt
                Try
                    Return executeUpdate(updateStockCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
                End Try
            End SyncLock
        End Function

Anmerkungen:

  • Zeile 1 – Die Methode erhält als Parameter die Artikelnummer, deren Bestand geändert werden soll, sowie die Bestandsänderung (positiv oder negativ). Sie gibt die Anzahl der durch den Vorgang c.a.d geänderten Zeilen zurück. 0 oder 1.
  • Zeilen 5–10: Die Abfrage [updateStockCommand] wird initialisiert. Zur Erinnerung: Der Text der Abfrage SQL lautet:
            updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)

Es ist zu beachten, dass der Bestand nur geändert wird, wenn er nach der Änderung >=0 bleibt.

  • Die Abfrage zur Aktualisierung des Artikelbestands wird in Zeile 13 ausgeführt und das Ergebnis ausgegeben.
  • In den Zeilen 14–18 wird eine mögliche Ausnahme behandelt

2.3.2. Erstellung der Assembly der Schicht [dao]

Das Visual Studio-Projekt dieser neuen Version der Schicht [dao] hat folgende Struktur:

Image

Das Projekt ist so konfiguriert, dass es eine DLL mit dem Namen [webarticles-dao.dll] generiert:

2.3.3. NUnit-Tests für die Schicht [dao]

2.3.3.1. Erstellung einer ODBC-Firebird-Datenquelle „

Um unsere neue Schicht [dao] zu testen, benötigen wir eine Datenquelle ODBC und somit eine Datenbank. Wir verwenden die Firebird-Datenbank SGBD (Abschnitt 3.5). Mit IBExpert (Abschnitt 3.6) erstellen wir die folgende Artikel-Datenbank:

Der Administrator dieser Datenbank ist der Benutzer [SYSDBA] mit dem Passwort [masterkey]. Wir legen einige Artikel an:

Image

Nun erstellen wir die folgende Firebird-Quelle ODBC (siehe Abschnitt 3.7):

Die erstellte Firebird-Quelle ODBC weist folgende Merkmale auf:

  • Name DSN: odbc-firebird-articles
  • Anmelde-ID: SYSDBA
  • zugehöriges Passwort: masterkey

2.3.3.2. Die Test sklasse NUnit

Wir haben bereits eine Testklasse für die ursprünglich erstellte Schicht [dao] geschrieben. Wie sich der Leser vielleicht erinnert, testete diese Klasse nicht eine bestimmte Klasse, sondern die Schnittstelle [IArticlesDao]:

Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO

Namespace istia.st.articles.tests

    <TestFixture()> _
    Public Class NunitTestArticlesArrayList
         ' Das zu testende Objekt
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' Eine Instanz des Spring-Objektgenerators wird abgerufen
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
             ' Die Instanziierung des Objekts „articlesdao“ wird angefordert
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

....

Man sieht, dass in der Attributmethode <Setup()> von Spring eine Referenz auf das Singleton mit dem Namen [articlesdao] vom Typ [IArticlesDao], also vom Typ der Schnittstelle, angefordert wird. Das Singleton [articlesdao] wurde durch die folgende Konfigurationsdatei [spring-config.xml] definiert:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao"/>
</objects>

Zeigen wir, dass wir mit der ursprünglichen Testklasse unsere neue Schicht [dao] ohne Änderungen oder Neukompilierung testen können.

  • Erstellen wir im Visual-Studio-Ordner unserer neuen Schicht [dao] den Ordner [tests] (unten rechts) an, indem wir den Ordner „[bin]“ aus dem Testprojekt der ursprünglichen Schicht „[dao]“ (unten links) kopieren. Bei Bedarf kann der Leser das Testprojekt der ersten Version der Schicht [dao] im ersten Teil des Artikels noch einmal nachlesen.
  • Im Ordner [tests] ersetzen wir die DLL und [webarticles-dao.dll] aus deralten Ebene [dao] durch die Elemente DLL und [webarticles-dao.dll] aus der neuen Ebene [dao]
  • Ändern wir die Konfigurationsdatei [spring-config.xml], um die neue Klasse [ArticlesDaoPlainODBC] zu instanziieren:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, webarticles-dao">
        <constructor-arg index="0">
            <value>odbc-firebird-articles</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>SYSDBA</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>masterkey</value>
        </constructor-arg>
    </object>
</objects>

Anmerkungen:

  • Zeile 6: Das Objekt [articlesdao] ist nun mit einer Instanz der Klasse [ ArticlesDaoPlainODBC] verknüpft
  • Diese Klasse verfügt über einen Konstruktor mit drei Argumenten:
  • den Namen der Quelle DSN – Zeile 8
  • die Identität, mit der auf die Datenbank zugegriffen wird – Zeile 11
  • das zu dieser Identität gehörende Passwort – Zeile 14

Wir übernehmen hier die Informationen aus der zuvor erstellten ODBC-Firebird-Quelle.

2.3.3.3. Tests

Wir sind nun bereit für die Tests. Mit Hilfe der Anwendung [Nunit-Gui] laden wir die Datei „DLL“ aus dem oben genannten Ordner „[tests]“ und führen den Test „[testGetAllArticles]“ aus:

Image

Wenn man sich den obigen Screenshot ansieht, könnte man den Namen [NUnitTestArticlesDaoArrayList] bedauern, der der Testklasse ursprünglich gegeben wurde. Das ist verwirrend. Tatsächlich wird hier die Klasse [ArticlesDaoPlainODBC] getestet. Der Screenshot zeigt, dass wir die Artikel, die wir in die Tabelle [ARTICLES] eingefügt hatten, korrekt abgerufen haben. Führen wir nun alle Tests durch:

Image

Im linken Fenster ist die Liste der getesteten Methoden zu sehen. Die Farbe des Punktes vor dem Namen jeder Methode zeigt an, ob die Methode erfolgreich (grün) oder fehlgeschlagen (rot) ist. Der Leser, der dieses Dokument am Bildschirm betrachtet, kann sehen, dass alle Tests erfolgreich waren.

2.3.3.4. Fazit

Wir haben soeben gezeigt, dass:

  • die Testklasse NUnit nicht auf eine Klasse, sondern auf eine Schnittstelle verwies;
  • weil der genaue Name der Instanzklasse der Schnittstelle in einer Konfigurationsdatei und nicht im Code angegeben war;
  • da Spring sich um die Instanziierung der Klasse kümmerte und dem Testcode eine Referenz darauf übermittelte;

blieb der für die ursprüngliche Schicht [dao] geschriebene Testcode für eine neue Implementierung derselben Schicht gültig. Wir benötigten keinen Zugriff auf den Code der Testklasse. Wir haben lediglich die kompilierte Version verwendet, die beim Testen der ursprünglichen Schicht [dao] generiert wurde. Wir werden ähnliche Schlussfolgerungen ziehen, wenn es darum geht, die neue Schicht [dao] in die Anwendung [webarticles] zu integrieren.

2.3.4. Integration der neuen Schicht [dao] in die Anwendung [webarticles]

2.3.4.1. Integrationstests

Zur Erinnerung: Die ursprüngliche Version der Anwendung [webarticles] war im folgenden Ordner [runtime] bereitgestellt worden:

 

Der Leser wird gebeten, gegebenenfalls Abschnitt 2.2.4 noch einmal durchzulesen, in dem die Modalitäten der Bereitstellung der Anwendung [webarticles] detailliert beschrieben sind. Wir nehmen folgende Änderungen am Inhalt des Ordners [runtime] vor:

  • Im Ordner [bin] wird die Datei DLL der alten Ebene [dao] durch die Datei DLL der neuen Ebene [dao] ersetzt
  • In [runtime] wird die Konfigurationsdatei [web.config] durch eine Datei ersetzt, die die neue Implementierungsklasse der Schicht [dao] berücksichtigt:

Die neue Konfigurationsdatei [web.config] lautet wie folgt:

<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, webarticles-dao">
                <constructor-arg index="0">
                    <value>odbc-firebird-articles</value>
                </constructor-arg>
                <constructor-arg index="1">
                    <value>SYSDBA</value>
                </constructor-arg>
                <constructor-arg index="2">
                    <value>masterkey</value>
                </constructor-arg>
            </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

Anmerkungen:

  • Die Zeilen 14–24 ordnen dem Singleton [articlesDao] eine Instanz der neuen Klasse [ArticlesDaoPlainODBC] zu. Dies ist die einzige Änderung. Wir sind bereits bei den Tests der neuen Schicht [dao] darauf gestoßen.

Wir sind bereit für die Tests. Wir konfigurieren den Webserver [Cassini] auf dieselbe Weise wie in Abschnitt 2.2.4. Wir initialisieren die Artikeltabelle [Firebird] mit den folgenden Werten:

Image

Stellen Sie sicher, dass der Cassini-Webserver sowie die Webserver SGBD und [Firebird] gestartet sind. Rufen Sie mit einem Browser die URL [http://localhost/webarticles/main.aspx] auf:

Image

Nun überprüfen wir den Inhalt der Tabelle [ARTICLES] in der Datenbank [Firebird]:

Image

Die Artikel [parapluie] und [bottes] wurden gekauft und ihre Bestände um die gekaufte Menge verringert. Der Artikel [chapeau] konnte nicht gekauft werden, da die angeforderte Menge den Lagerbestand überstieg. Wir laden den Leser ein, weitere Tests durchzuführen.

2.3.4.2. Fazit

Was haben wir getan?

  • Wir haben die Bereitstellungsversion der alten Version übernommen;
  • wir haben die DLL der Ebene [dao] durch eine neue Version ersetzt. Die DLL der Schichten [web] und [domain] blieben unverändert;
  • wir haben die Konfigurationsdatei [web.config] so angepasst, dass sie die neue Implementierungsklasse der Schicht [dao] berücksichtigt

All dies ist übersichtlich und bietet der Webanwendung große Flexibilität für zukünftige Erweiterungen. Diese wichtigen Eigenschaften werden durch zwei architektonische Entscheidungen ermöglicht:

  • dem Zugriff auf die Schichten über Schnittstellen
  • die Integration und Konfiguration der Schichten durch Spring.

Wir schlagen nun eine neue Implementierung der Schicht [dao] vor.

2.4. Die Implementierungsklasse [ArticlesDaoSqlServer]

Die zweite Implementierung der Schicht [dao] setzt voraus, dass sich die Daten in einer SQL-Server-Datenbank befinden. Microsoft stellt einen SGBD namens MSDE zur Verfügung, bei dem es sich um eine eingeschränkte Version des SQL-Servers handelt. Im Anhang, Abschnitt 3.12, wird beschrieben, wie man ihn erhält und installiert.

2.4.1. Der Code

Die Klasse [ArticlesDaoSqlServer] ist der zuvor behandelten Klasse [ArticlesDaoPlainODBC] sehr ähnlich. Daher werden wir nur die Änderungen gegenüber der vorherigen Version aufführen:

  • Die erforderlichen Klassen befinden sich im Namensraum [System.Data.SqlClient] anstelle des Namensraums [System.Data.Odbc]
  • Die Verbindung vom Typ [OdbcConnection] hat nun den Typ [SqlConnection]
  • Die Objekte vom Typ [OdbcCommand] haben nun den Typ [SqlCommand]
  • Die Syntax der parametrisierten Abfragen vom Typ SQL ändert sich. Die Einfügeabfrage lautet nun wie folgt:
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)

während sie zuvor wie folgt lautete:

insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
  • Die Methode [ajouteArticle] lautet nun wie folgt:
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' Exklusivbereich
            SyncLock Me
                 ' Die Einfügeanfrage wird vorbereitet
                With insertCommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@id", unArticle.id))
                    .Add(New SqlParameter("@nom", unArticle.nom))
                    .Add(New SqlParameter("@prix", unArticle.prix))
                    .Add(New SqlParameter("@sa", unArticle.stockactuel))
                    .Add(New SqlParameter("@sm", unArticle.stockminimum))
                End With
                Try
                     ' Die Abfrage wird ausgeführt
                    Return executeUpdate(insertCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
                End Try
            End SyncLock
        End Function
  • Der Hersteller wird ebenfalls geändert:
        Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
             ' Server: Name der Instanz SQL, die erreicht werden soll
             ' databaseName: Name der anzusteuernden Datenbank
             ' uid: Benutzer-ID
             ' Passwort: sein Passwort

             ': Der als Argument übergebene Name der Datenbank wird abgerufen
            Me.databaseName = databaseName
             ': Die Verbindung wird instanziiert
            Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
            connexion = New SqlConnection(connectString)
             ' Die Abfragen werden vorbereitet SQL
            insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
...
        End Sub

Der Konstruktor akzeptiert nun vier Parameter:

             ' Server: Name der Instanz SQL, die erreicht werden soll
             ' databaseName: Name der anzusteuernden Datenbank
             ' uid: Benutzer-ID
             ' Passwort: sein Passwort

Der vollständige Code der Klasse [ArticlesDaoSqlServer] lautet wie folgt:

Imports System
Imports System.Collections
Imports System.Data.SqlClient

Namespace istia.st.articles.dao

    Public Class ArticlesDaoSqlServer
        Implements istia.st.articles.dao.IArticlesDao

         ' private Felder
        Private connexion As SqlConnection = Nothing
        Private databaseName As String
        Private insertCommand As SqlCommand
        Private updatecommand As SqlCommand
        Private deleteSomeCommand As SqlCommand
        Private selectSomeCommand As SqlCommand
        Private updateStockCommand As SqlCommand
        Private deleteAllCommand As SqlCommand
        Private selectAllCommand As SqlCommand

         ' Hersteller
        Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
             ' Server: Name der zu erreichenden Instanz SQL
             ' databaseName: Name der anzusteuernden Datenbank
             ' uid: Benutzer-ID
             ' Passwort: sein Passwort

             ': Der als Argument übergebene Name der Datenbank wird abgerufen
            Me.databaseName = databaseName
             ': Die Verbindung wird hergestellt
            Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
            connexion = New SqlConnection(connectString)
             ' Die Abfragen werden vorbereitet SQL
            insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
            updatecommand = New SqlCommand("update ARTICLES set nom=@nom, prix=@prix, stockactuel=@sa, stockminimum=@sm where id=@id", connexion)
            deleteSomeCommand = New SqlCommand("delete from ARTICLES where id=@id", connexion)
            selectSomeCommand = New SqlCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=@id", connexion)
            updateStockCommand = New SqlCommand("update ARTICLES set stockactuel=stockactuel+@mvt where id=@id and (stockactuel+@mvt)>=0", connexion)
            selectAllCommand = New SqlCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
            deleteAllCommand = New SqlCommand("delete from ARTICLES", connexion)
        End Sub

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Die Einfügeabfrage wird vorbereitet
                With insertCommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@id", unArticle.id))
                    .Add(New SqlParameter("@nom", unArticle.nom))
                    .Add(New SqlParameter("@prix", unArticle.prix))
                    .Add(New SqlParameter("@sa", unArticle.stockactuel))
                    .Add(New SqlParameter("@sm", unArticle.stockminimum))
                End With
                Try
                     'wird ausgeführt
                    Return executeUpdate(insertCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Aktualisierungsanfrage für den Lagerbestand wird vorbereitet
                With updateStockCommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@mvt", mouvement))
                    .Add(New SqlParameter("@id", idArticle))
                End With
                 'wird ausgeführt
                Try
                    Return executeUpdate(updateStockCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' exklusiver Abschnitt
            SyncLock Me
                Try
                     'Die Einfügeanfrage wird ausgeführt
                    executeUpdate(deleteAllCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
                End Try
            End SyncLock
        End Sub

        Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
             ' exklusiver Abschnitt
            SyncLock Me
                Try
                     'Die SELECT-Abfrage wird ausgeführt
                    Dim articles As IList = executeQuery(selectAllCommand)
                     'Die Liste wird zurückgegeben
                    Return articles
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Die SELECT-Abfrage wird vorbereitet
                With selectSomeCommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@id", idArticle))
                End With
                 ': Die Abfrage wird ausgeführt
                Try
                     'Die Abfrage wird ausgeführt
                    Dim articles As IList = executeQuery(selectSomeCommand)
                     'Es wird geprüft, ob der Artikel gefunden wurde
                    If articles.Count = 0 Then Return Nothing
                     'Der Artikel wird zurückgegeben
                    Return CType(articles.Item(0), Article)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Die Update-Abfrage wird vorbereitet
                With updatecommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@nom", unArticle.nom))
                    .Add(New SqlParameter("@prix", unArticle.prix))
                    .Add(New SqlParameter("@sa", unArticle.stockactuel))
                    .Add(New SqlParameter("@sm", unArticle.stockminimum))
                    .Add(New SqlParameter("@id", unArticle.id))
                End With
                 ' die Abfrage wird ausgeführt
                Try
                     'Die Einfügeabfrage wird ausgeführt
                    Return executeUpdate(updatecommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
                End Try
            End SyncLock
        End Function

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' Exklusivbereich
            SyncLock Me
                 ' Die DELETE-Abfrage wird vorbereitet
                With deleteSomeCommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@id", idArticle))
                End With
                 'wird ausgeführt
                Try
                     'Die Löschanfrage wird ausgeführt
                    Return executeUpdate(deleteSomeCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Private Function executeQuery(ByVal query As SqlCommand) As IList
             ' Ausführung einer Abfrage SELECT 
             ' Deklaration des Objekts, das den Zugriff auf alle Zeilen der Ergebnistabelle ermöglicht
            Dim myReader As SqlDataReader = Nothing
            Try
                 ': Es wird eine Verbindung zu BDD hergestellt
                connexion.Open()
                 ': Die Abfrage wird ausgeführt
                myReader = query.ExecuteReader()
                 ': Deklaration einer Artikelliste, um diese anschließend zurückzugeben
                Dim articles As IList = New ArrayList
                Dim unArticle As Article
                While myReader.Read()
                     ': Ein Artikel wird mit den Werten des Lesegeräts vorbereitet
                    unArticle = New Article
                    unArticle.id = myReader.GetInt32(0)
                    unArticle.nom = myReader.GetString(1)
                    unArticle.prix = myReader.GetDouble(2)
                    unArticle.stockactuel = myReader.GetInt32(3)
                    unArticle.stockminimum = myReader.GetInt32(4)
                     ': Der Artikel wird zur Liste hinzugefügt
                    articles.Add(unArticle)
                End While
                 ': Das Ergebnis wird zurückgegeben
                Return articles
            Finally
                 ' Freigabe der Ressourcen
                If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function

        Private Function executeUpdate(ByVal updateCommand As SqlCommand) As Integer
             ' Ausführung einer Aktualisierungsanfrage
            Try
                 ': Es wird eine Verbindung zur BDD hergestellt
                connexion.Open()
                 'Die Abfrage wird ausgeführt
                Return updateCommand.ExecuteNonQuery()
            Finally
                 ' Freigabe der Ressourcen
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function
    End Class

End Namespace

Der Leser wird gebeten, diesen Code im Zusammenhang mit den zuvor gemachten Anmerkungen zur Klasse [ArticlesDaoPlainODBC] zu lesen.

2.4.2. Erstellung der Assembly der Schicht [dao]

Das neue Visual Studio-Projekt hat folgende Struktur:

Image

Das Projekt ist so konfiguriert, dass es eine DLL mit dem Namen [webarticles-dao.dll] generiert:

2.4.3. NUnit-Tests für die Schicht [dao]

2.4.3.1. Erstellung eines SQL-Server- s als Datenquelle

Um unsere neue Schicht [dao] zu testen, benötigen wir eine Datenquelle SQL Server und somit den SGBD SQL Server. Wir werden tatsächlich den SGBD MSDE (MicroSoft Data Engine) (Abschnitt 3.12), bei der es sich um eine Version des SQL-Servers handelt, die lediglich durch die Anzahl der gleichzeitig zugelassenen Benutzer begrenzt ist. Mit [EMS MS SQL Manager] (Absatz 3.14) legen wir die folgende Artikelstammdatenbank in einer MSDE-Instanz namens [portable1_tahe\msde140405] an:

Image

Die Datenbank gehört dem Benutzer [mdparticles] mit dem Passwort [admarticles]. Der Transact-SQL-Befehl zum Anlegen der Tabelle [ARTICLES] lautet wie folgt:

CREATE TABLE [ARTICLES] (
  [id] int NOT NULL,
  [nom] varchar(20) COLLATE French_CI_AS NOT NULL,
  [prix] float(53) NOT NULL,
  [stockactuel] int NOT NULL,
  [stockminimum] int NOT NULL,
  CONSTRAINT [ARTICLES_uq] UNIQUE ([nom]),
  PRIMARY KEY ([id]),
  CONSTRAINT [ARTICLES_ck_id] CHECK ([id] > 0),
  CONSTRAINT [ARTICLES_ck_nom] CHECK ([nom] <> ''),
  CONSTRAINT [ARTICLES_ck_prix] CHECK ([prix] >= 0),
  CONSTRAINT [ARTICLES_ck_stockactuel] CHECK ([stockactuel] >= 0),
  CONSTRAINT [ARTICLES_ck_stockminimum] CHECK ([stockminimum] >= 0)
)
ON [PRIMARY]
GO

Wir erstellen einige Artikel:

Image

2.4.3.2. Die Testklasse NUnit

Die NUnit-Testklasse der Implementierungsklasse [ArticlesDaoSqlServer] ist dieselbe wie die der Klasse [ArticlesDaoPlainODBC] (siehe Abschnitt 2.3.3.2). Wir gehen analog vor, um den NUnit-Test für die Klasse vorzubereiten:

  • Wir erstellen im Visual-Studio-Ordner des Projekts [dao-sqlserver] den Ordner [tests] (rechts), indem wir den Ordner [tests] aus dem Projekt [dao-odbc] (links) kopieren:
  • im Ordner [tests] des Projekts [dao-sqlserver], ersetzen wir die Dateien DLL und [webarticles-dao.dll] durch die Dateien DLL und [webarticles-dao.dll], die bei der Generierung des Projekts [dao-sqlserver] entstanden sind
  • wir ändern die Konfigurationsdatei [spring-config.xml], um die neue Klasse [ArticlesDaoSqlServer] zu instanziieren:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!--
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
-->
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoSqlServer, webarticles-dao">
        <constructor-arg index="0">
            <value>portable1_tahe\msde140405</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>dbarticles</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>admarticles</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>mdparticles</value>
        </constructor-arg>
    </object>
</objects>

Anmerkungen:

  • Zeile 7: Das Objekt [articlesdao] ist nun mit einer Instanz der Klasse [ ArticlesDaoSqlServeur] verknüpft
  • Diese Klasse verfügt über einen Konstruktor mit vier Argumenten:
  • den Namen der verwendeten Instanz MSDE – Zeile 9
  • den Namen der Datenbank – Zeile 12
  • die Identität, mit der auf die Datenbank zugegriffen wird – Zeile 15
  • das zu dieser Identität gehörende Passwort – Zeile 18

Wir übernehmen hier die Informationen aus der zuvor erstellten Quelle MSDE.

2.4.3.3. Tests

Wir sind bereit für die Tests. Mit Hilfe der Anwendung [Nunit-Gui] laden wir die Datei DLL aus dem oben genannten Ordner [tests] und führen den Test [testGetAllArticles] aus:

Image

Obwohl der Testklasse ursprünglich der Name [NUnitTestArticlesDaoArrayList] gegeben wurde – der beibehalten wurde, da wir die aus dieser Klasse stammenden DLL und [tests-webarticles-dao.dll] verwenden –, wird hier tatsächlich die Klasse [ArticlesDaoSqlserver] getestet. Der Screenshot zeigt, dass wir die Artikel, die wir in die Tabelle [ARTICLES] eingefügt hatten, korrekt abgerufen haben. Führen wir nun alle Tests durch:

Image

Im linken Fenster sieht man die Liste der getesteten Methoden. Die Farbe des Punktes vor dem Namen jeder Methode zeigt an, ob die Methode erfolgreich (grün) oder fehlgeschlagen (rot) ist. Der Leser, der dieses Dokument auf dem Bildschirm betrachtet, kann sehen, dass alle Tests erfolgreich waren.

2.4.4. Integration der neuen Schicht [dao] in die Anwendung [webarticles]

Wir folgen dem in Abschnitt 2.3.4 erläuterten Vorgehen. Wir nehmen folgende Änderungen am Inhalt des Ordners [runtime] vor:

  • Im Ordner [bin] wird die DLL deralten Ebene [dao] wird durch die Datei DLL der neuen Ebene [dao] ersetzt, die durch die Klasse [ArticlesDaoSqlServer] implementiert wird
  • In [runtime] wird die Konfigurationsdatei [web.config] durch eine Datei ersetzt, die die neue Implementierungsklasse berücksichtigt:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoSqlServer, webarticles-dao">
        <constructor-arg index="0">
            <value>portable1_tahe\msde140405</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>dbarticles</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>admarticles</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>mdparticles</value>
        </constructor-arg>
    </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

Anmerkungen:

  • In den Zeilen 15–33 wird dem Singleton [articlesDao] eine Instanz der neuen Klasse [ArticlesDaoSqlServer] zugeordnet. Dies ist die einzige Änderung. Wir sind bereits bei den Tests der neuen Schicht [dao] darauf gestoßen

Wir sind bereit für die Tests. Wir behalten die gleiche Konfiguration des Webservers [Cassini] wie zuvor bei. Wir initialisieren die Artikeltabelle [MSDE] mit den folgenden Werten:

Image

Stellen Sie sicher, dass der Cassini-Webserver sowie die Instanzen SGBD und MSDE (hier die Instanz portable1_tahe\msde140405) gestartet sind. Mit einem Browser rufen wir die Seiten URL und [http://localhost/webarticles/main.aspx] auf:

Image

Nun überprüfen wir den Inhalt der Tabelle [ARTICLES] in der Datenbank [MSDE]:

Image

Die Artikel [ballon foot] und [raquette tennis] wurden gekauft und ihre Bestände um die gekaufte Menge verringert. Der Artikel [rollers] konnte nicht gekauft werden, da die angeforderte Menge den Lagerbestand überstieg. Wir laden den Leser ein, weitere Tests durchzuführen.

2.4.5. Die Implementierungsklasse [ArticlesDaoOleDb]

2.4.5.1. Die Datenquellen- en OleDb

Die dritte Implementierung der Schicht [dao] setzt voraus, dass sich die Daten in einer Datenbank befinden, auf die über einen Treiber OleDb zugegriffen werden kann. Das Prinzip der Quellen OleDb entspricht dem der Quellen ODBC. Ein Programm, das eine Quelle vom Typ OleDb nutzt, tut dies über eine Standardschnittstelle, die allen Quellen vom Typ OleDb gemeinsam ist. Der Wechsel der Quelle OleDb läuft auf einen Wechsel des Treibers OleDb hinaus. Der Code selbst wird dabei nicht geändert.

Mit Visual Studio können Sie die auf Ihrem Rechner verfügbaren Treiber OleDb ermitteln:

  • Rufen Sie den Server-Explorer über [Affichage/Explorateur de serveurs] auf:

Image

  • Um eine neue Verbindung hinzuzufügen, klicken Sie mit der rechten Maustaste auf [Connexion de données] und wählen Sie die Option [Ajouter une connexion]. Daraufhin erscheint ein Assistent, mit dem Sie die Eigenschaften der Verbindung festlegen können:

Image

  • Das Fenster „[Fournisseur]“ zeigt eine Liste der verfügbaren Treiber „OLEDB“ an. Für die neue Ebene [dao] verwenden wir einen Treiber [Microsoft Jet 4.0 OLE DB Provider], der Zugriff auf die Datenbanken ACCESS gewährt.
  • Verlassen wir vorübergehend Visual Studio, um die Basis ACCESS [articles.mdb] mit der folgenden einzigen Tabelle anzulegen:

Image

  • Die Struktur der Tabelle ist wie folgt:
id
numerisch – Ganzzahl – Primärschlüssel
nom
Text – 20 Zeichen –
prix
Zahlen – Gleitkommazahl (Double)
stockactuel
Zahl – Ganzzahl
stockminimum
Zahl – Ganzzahl
  • Kehren wir zu Visual Studio zurück und erstellen wir eine neue Verbindung, wie zuvor beschrieben:

Image

  • Wir wählen den Treiber [Microsoft Jet 4.0] aus und wechseln zum Fenster [Connexion]:

Image

  • Wählen Sie über die Schaltfläche „[1]“ die soeben erstellte Datenbank „ACCESS“ aus und schließen Sie die Definition der Verbindung mit der Schaltfläche „[Terminer]“ ab. Die erstellte Verbindung erscheint nun in der Liste der verfügbaren Verbindungen:

Image

  • Ein Doppelklick auf die Tabelle [ARTICLES] gewährt Zugriff auf deren Inhalt:

Image

  • Nun können Sie Zeilen in der Tabelle hinzufügen, ändern oder löschen.
  • Wählen Sie im Server-Explorer die neue Verbindung aus, um auf deren Eigenschaftenblatt zuzugreifen:

Image

  • Es ist hilfreich, die Verbindungszeichenfolge zu kennen. Sie wird uns dienen, um eine Verbindung zur Datenbank herzustellen:
Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=D:\data\serge\databases\access\articles\articles.mdb;Mode=Share Deny None;Extended Properties="";Jet OLEDB:System database="";Jet OLEDB:Registry Path="";Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False
  • Von dieser Zeichenfolge behalten wir nur die folgenden Elemente im Gedächtnis:
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;

2.4.5.2. Der Klassencode [ArticlesDaoOleDb]

Die Klasse [ArticlesDaoOleDb]] ist der zuvor behandelten Klasse [ArticlesDaoPlainODBC] sehr ähnlich. Daher werden wir nur die Änderungen gegenüber der vorherigen Version angeben:

  • Die erforderlichen Klassen befinden sich im Namensraum [System.Data.OleDb] anstelle des Namensraums [System.Data.Odbc]
  • Die Verbindung vom Typ [OdbcConnection] hat nun den Typ [OleDbConnection]
  • Die Objekte vom Typ [OdbcCommand] haben nun den Typ [OleDbCommand]

Der Konstruktor der Klasse akzeptiert als einzigen Parameter die Verbindungszeichenfolge zur Datenbank:

         ' Konstruktor
        Public Sub New(ByVal connectString As String)
             ' connectString: Verbindungszeichenfolge zur Quelle OleDb
             ': Die Verbindung wird instanziiert
            connexion = New OleDbConnection(connectString)
             ' die Abfragen werden vorbereitet SQL
...
        End Sub

Der vollständige Code der Klasse [ArticlesDaoOleDb] lautet wie folgt:

Imports System
Imports System.Collections
Imports System.Data.OleDb

Namespace istia.st.articles.dao

    Public Class ArticlesDaoOleDb
        Implements istia.st.articles.dao.IArticlesDao

         ' private Felder
        Private connexion As OleDbConnection = Nothing
        Private insertCommand As OleDbCommand
        Private updatecommand As OleDbCommand
        Private deleteSomeCommand As OleDbCommand
        Private selectSomeCommand As OleDbCommand
        Private updateStockCommand As OleDbCommand
        Private deleteAllCommand As OleDbCommand
        Private selectAllCommand As OleDbCommand

         ' Konstruktor
        Public Sub New(ByVal connectString As String)
             ' connectString: Verbindungszeichenfolge zur Quelle OleDb
             ': Die Verbindung wird instanziiert
            connexion = New OleDbConnection(connectString)
             ' die Abfragen werden vorbereitet SQL
            insertCommand = New OleDbCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
            updatecommand = New OleDbCommand("update ARTICLES set nom=?, prix=?, stockactuel=?, stockminimum=? where id=?", connexion)
            deleteSomeCommand = New OleDbCommand("delete from ARTICLES where id=?", connexion)
            selectSomeCommand = New OleDbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=?", connexion)
            updateStockCommand = New OleDbCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
            selectAllCommand = New OleDbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
            deleteAllCommand = New OleDbCommand("delete from ARTICLES", connexion)
        End Sub

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Die Einfügeabfrage wird vorbereitet
                With insertCommand.Parameters
                    .Clear()
                    .Add(New OleDbParameter("id", unArticle.id))
                    .Add(New OleDbParameter("nom", unArticle.nom))
                    .Add(New OleDbParameter("prix", unArticle.prix))
                    .Add(New OleDbParameter("stockactuel", unArticle.stockactuel))
                    .Add(New OleDbParameter("stockminimum", unArticle.stockminimum))
                End With
                Try
                     'wird ausgeführt
                    Return executeUpdate(insertCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Aktualisierungsanfrage für den Lagerbestand wird vorbereitet
                With updateStockCommand.Parameters
                    .Clear()
                    .Add(New OleDbParameter("mvt1", mouvement))
                    .Add(New OleDbParameter("id", idArticle))
                    .Add(New OleDbParameter("mvt2", mouvement))
                End With
                 'wird ausgeführt
                Try
                    Return executeUpdate(updateStockCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' exklusiver Abschnitt
            SyncLock Me
                Try
                     'Die Einfügeanfrage wird ausgeführt
                    executeUpdate(deleteAllCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
                End Try
            End SyncLock
        End Sub

        Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
             ' exklusiver Abschnitt
            SyncLock Me
                Try
                     'Die SELECT-Abfrage wird ausgeführt
                    Dim articles As IList = executeQuery(selectAllCommand)
                     '– Die Liste wird zurückgegeben
                    Return articles
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Die SELECT-Abfrage wird vorbereitet
                With selectSomeCommand.Parameters
                    .Clear()
                    .Add(New OleDbParameter("id", idArticle))
                End With
                 'Die Abfrage wird ausgeführt
                Try
                     'Die Abfrage wird ausgeführt
                    Dim articles As IList = executeQuery(selectSomeCommand)
                     'Es wird geprüft, ob der Artikel gefunden wurde
                    If articles.Count = 0 Then Return Nothing
                     'Der Artikel wird zurückgegeben
                    Return CType(articles.Item(0), Article)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Die Update-Abfrage wird vorbereitet
                With updatecommand.Parameters
                    .Clear()
                    .Add(New OleDbParameter("nom", unArticle.nom))
                    .Add(New OleDbParameter("prix", unArticle.prix))
                    .Add(New OleDbParameter("stockactuel", unArticle.stockactuel))
                    .Add(New OleDbParameter("stockminimum", unArticle.stockactuel))
                    .Add(New OleDbParameter("id", unArticle.id))
                End With
                 ' die Abfrage wird ausgeführt
                Try
                     'Die Einfügeabfrage wird ausgeführt
                    Return executeUpdate(updatecommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
                End Try
            End SyncLock
        End Function

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' Exklusivbereich
            SyncLock Me
                 ' Die DELETE-Abfrage wird vorbereitet
                With deleteSomeCommand.Parameters
                    .Clear()
                    .Add(New OleDbParameter("id", idArticle))
                End With
                 'wird ausgeführt
                Try
                     'Die Löschanfrage wird ausgeführt
                    Return executeUpdate(deleteSomeCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Private Function executeQuery(ByVal query As OleDbCommand) As IList
             ' Ausführung einer Abfrage SELECT 
             ' Deklaration des Objekts, das den Zugriff auf alle Zeilen der Ergebnistabelle ermöglicht
            Dim myReader As OleDbDataReader = Nothing
            Try
                 ': Es wird eine Verbindung zu BDD hergestellt
                connexion.Open()
                 ': Die Abfrage wird ausgeführt
                myReader = query.ExecuteReader()
                 ': Deklaration einer Artikelliste, um diese anschließend zurückzugeben
                Dim articles As IList = New ArrayList
                Dim unArticle As Article
                While myReader.Read()
                     ': Ein Artikel wird mit den Werten des Lesegeräts vorbereitet
                    unArticle = New Article
                    unArticle.id = myReader.GetInt32(0)
                    unArticle.nom = myReader.GetString(1)
                    unArticle.prix = myReader.GetDouble(2)
                    unArticle.stockactuel = myReader.GetInt32(3)
                    unArticle.stockminimum = myReader.GetInt32(4)
                     ': Der Artikel wird zur Liste hinzugefügt
                    articles.Add(unArticle)
                End While
                 ': Das Ergebnis wird zurückgegeben
                Return articles
            Finally
                 ' Freigabe der Ressourcen
                If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function

        Private Function executeUpdate(ByVal sqlCommand As OleDbCommand) As Integer
             ' Ausführung einer Aktualisierungsanfrage
            Try
                 ': Es wird eine Verbindung zur BDD hergestellt
                connexion.Open()
                 'Die Abfrage wird ausgeführt
                Return sqlCommand.ExecuteNonQuery()
            Finally
                 ' Freigabe der Ressourcen
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function
    End Class

End Namespace

Der Leser wird gebeten, diesen Code im Zusammenhang mit den zuvor gemachten Anmerkungen zur Klasse [ArticlesDaoPlainODBC] zu lesen.

2.4.5.3. Erstellung der Assembly der Schicht [dao]

Das neue Visual Studio-Projekt hat folgende Struktur:

Image

Das Projekt ist so konfiguriert, dass es eine DLL mit dem Namen [webarticles-dao.dll] generiert:

2.4.5.4. NUnit-Tests für die Schicht [dao]

2.4.5.4.1. Die Testklasse NUnit

Die NUnit-Testklasse für die Implementierungsklasse [ArticlesDaoOleDb] ist dieselbe wie die der Klasse [ArticlesDaoPlainODBC] (siehe Abschnitt 2.3.3.2). Wir gehen analog vor, um den NUnit-Test für die Klasse vorzubereiten:

  • Wir erstellen im Visual-Studio-Ordner des Projekts [dao-oledb] den Ordner [tests] (rechts), indem wir den Ordner [tests] aus dem Projekt [dao-odbc] (links) kopieren:
  • Im Ordner [tests] des Projekts [dao-oledb] ersetzen wir die Ordner DLL und [webarticles-dao.dll] durch die Ordner DLL und [webarticles-dao.dll], die aus der Generierung des Projekts [dao-oledb] stammt
  • Wir ändern die Konfigurationsdatei [spring-config.xml], um die neue Klasse [ArticlesDaoOleDb] zu instanziieren:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!--
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
-->
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoOleDb, webarticles-dao">
        <constructor-arg index="0">
            <value>Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;</value>
        </constructor-arg>
    </object>
</objects>

Anmerkungen:

  • Zeile 7: Das Objekt [articlesdao] ist nun mit einer Instanz der Klasse [ ArticlesDaoOleDb] verknüpft
  • Diese Klasse verfügt über einen Konstruktor mit einem Argument: die Verbindungszeichenfolge zur Datenbank OleDb ACCESS – Zeile 9
2.4.5.4.2. Tests

Wir sind bereit für die Tests. Mit Hilfe der Anwendung [Nunit-Gui] laden wir die Dateien DLL und [test-webarticles-dao.dll] aus dem oben genannten Ordner [tests] und führen den Test [testGetAllArticles] aus:

Image

Obwohl der Testklasse ursprünglich der Name [NUnitTestArticlesDaoArrayList] zugewiesen wurde, wird hier tatsächlich die Klasse [ArticlesDaoOleDb] getestet. Der Screenshot zeigt, dass wir die Artikel, die wir in die Tabelle [ARTICLES] eingefügt hatten, korrekt abgerufen haben. Führen wir nun alle Tests durch:

Image

Der Leser, der dieses Dokument auf dem Bildschirm betrachtet, kann sehen, dass alle Tests erfolgreich waren (grüne Farbe).

2.4.5.5. Integration der neuen Schicht [dao] in die Anwendung [webarticles]

Wir folgen der in Abschnitt 2.3.4 beschriebenen Vorgehensweise. Wir nehmen folgende Änderungen am Inhalt des Ordners [runtime] vor:

  • Im Ordner „[bin]“ wird die Datei „DLL“ aus deralten Ebene [dao] wird durch die Datei DLL der neuen Ebene [dao] ersetzt, die von der Klasse [ArticlesDaoOleDb] implementiert wird
  • in [runtime] wird die Konfigurationsdatei [web.config] durch eine Datei ersetzt, die die neue Implementierungsklasse berücksichtigt:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoOleDb, webarticles-dao">
                <constructor-arg index="0">
                    <value>Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;</value>
                </constructor-arg>
            </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

Anmerkungen:

  • In den Zeilen 14–18 wird dem Singleton [articlesDao] eine Instanz der neuen Klasse [ArticlesDaoOleDb] zugeordnet. Dies ist die einzige Änderung.

Wir behalten die gleiche Konfiguration des Webservers [Cassini] wie zuvor bei. Wir initialisieren die Artikeltabelle mit den folgenden Werten:

Image

Stellen Sie sicher, dass die Artikeldatenbank nicht von einem Programm wie Visual Studio oder ACCESS verwendet wird. Mit einem Browser rufen wir die Seiten URL und [http://localhost/webarticles/main.aspx] auf:

Image

Nun überprüfen wir den Inhalt der Tabelle [ARTICLES] mit ACCESS:

Image

Die Artikel [pantalon] und [jupe] wurden gekauft und ihre Bestände um die gekaufte Menge verringert. Der Artikel [manteau] konnte nicht gekauft werden, da die angeforderte Menge den Lagerbestand überstieg. Wir laden den Leser ein, weitere Tests durchzuführen.

2.5. Die Implementierungsklasse [ArticlesDaoFirebirdProvider]

2.5.1. Der Firebird-net-provider

Wir haben bereits eine Datenquelle [Firebird] verwendet, auf die wir über einen Treiber ODBC zugegriffen haben. Die Treiber ODBC bieten zwar eine hohe Wiederverwendbarkeit des Codes, der sie nutzt, sind jedoch weniger leistungsfähig als Treiber, die speziell für das Zielsystem SGBD geschrieben wurden. Der SGBD [Firebird] kann über eine Bibliothek mit spezifischen Klassen verwendet werden, die auf der Firebird-Website [http://firebird.sourceforge.net/] heruntergeladen werden kann. Die Download-Seite bietet die folgenden Links (Stand: April 2005):

Image

Über den Link [firebird-net-provider] können die .NET-Klassen für den Zugriff auf Firebird (SGBD) heruntergeladen werden. Nach der Installation des Pakets wird ein Ordner erstellt, der in etwa wie folgt aussieht:

Image

Zwei Elemente sind für uns von Interesse:

  • [FirebirdSql.Data.Firebird.dll]: Die Assembly, die die .NET-Klassen für den Zugriff auf SGBD Firebird enthält
  • [FirebirdNETProviderSDK.chm]: die Dokumentation zu diesen Klassen

Damit ein Visual Studio-Projekt diese Klassen anschließend nutzen kann, führen wir zwei Schritte durch:

  • Wir legen die Assembly [FirebirdSql.Data.Firebird.dll] im Ordner [bin] des Projekts ab
  • Wir fügen dieselbe Assembly zu den Referenzen des Projekts hinzu

2.5.2. Der Code der Klasse [ArticlesDaoFirebirdProvider]

Die Klasse [ArticlesDaoFirebirdProvider] ähnelt stark der zuvor behandelten Klasse [ArticlesDaoSqlServer]. Daher werden wir nur die Änderungen gegenüber dieser Version aufführen:

  • Die erforderlichen Klassen befinden sich im Namensraum [FirebirdSql.Data.Firebird] statt im Namensraum [System.Data.SqlClient]
  • Die Verbindung vom Typ [SqlConnection] hat nun den Typ [FbConnection]
  • Die Objekte vom Typ [SqlCommand] haben nun den Typ [FbCommand]
  • Die Objekte vom Typ [SqlParameter] haben nun den Typ [FbParameter]

Der Klassenkonstruktor akzeptiert vier Parameter, mit denen er die Verbindungszeichenfolge zur Datenbank erstellt:

         ' Konstruktor
        Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
             ' Server: Name des Host-Rechners von SGBD
             ' databaseName: Pfad zur Datenbank
             ' uid: ID des sich anmeldenden Benutzers
             ' Passwort: sein Passwort
...
        End Sub

Der vollständige Code der Klasse [ArticlesDaoFirebirdProvider] lautet wie folgt:

Imports System
Imports System.Collections
Imports FirebirdSql.Data.Firebird

Namespace istia.st.articles.dao

    Public Class ArticlesDaoFirebirdProvider
        Implements istia.st.articles.dao.IArticlesDao

         ' private Felder
        Private connexion As FbConnection = Nothing
        Private databasePath As String
        Private insertCommand As FbCommand
        Private updatecommand As FbCommand
        Private deleteSomeCommand As FbCommand
        Private selectSomeCommand As FbCommand
        Private updateStockCommand As FbCommand
        Private deleteAllCommand As FbCommand
        Private selectAllCommand As FbCommand

         ' Hersteller
        Public Sub New(ByVal serveur As String, ByVal databasePath As String, ByVal uid As String, ByVal password As String)
             ' Server: Name des Host-Rechners von SGBD Firebird
             ' databaseName: Pfad zur zu verarbeitenden Datenbank
             ' uid: Benutzername des Benutzers, der sich bei der Datenbank anmeldet
             ' password: sein Passwort

             ': Der als Argument übergebene Name der Datenbank wird abgerufen
            Me.databasePath = databasePath
             ': Die Verbindung wird hergestellt
            Dim connectString As String = String.Format("DataSource={0};Database={1};User={2};Password={3}", serveur, databasePath, uid, password)
            connexion = New FbConnection(connectString)
             ' Die Abfragen werden vorbereitet SQL
            insertCommand = New FbCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
            updatecommand = New FbCommand("update ARTICLES set nom=@nom, prix=@prix, stockactuel=@sa, stockminimum=@sm where id=@id", connexion)
            deleteSomeCommand = New FbCommand("delete from ARTICLES where id=@id", connexion)
            selectSomeCommand = New FbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=@id", connexion)
            updateStockCommand = New FbCommand("update ARTICLES set stockactuel=stockactuel+@mvt where id=@id and (stockactuel+@mvt)>=0", connexion)
            selectAllCommand = New FbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
            deleteAllCommand = New FbCommand("delete from ARTICLES", connexion)
        End Sub

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Die Einfügeabfrage wird vorbereitet
                With insertCommand.Parameters
                    .Clear()
                    .Add(New FbParameter("@id", unArticle.id))
                    .Add(New FbParameter("@nom", unArticle.nom))
                    .Add(New FbParameter("@prix", unArticle.prix))
                    .Add(New FbParameter("@sa", unArticle.stockactuel))
                    .Add(New FbParameter("@sm", unArticle.stockminimum))
                End With
                Try
                     'wird ausgeführt
                    Return executeUpdate(insertCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Aktualisierungsanfrage für den Lagerbestand wird vorbereitet
                With updateStockCommand.Parameters
                    .Clear()
                    .Add(New FbParameter("@mvt", mouvement))
                    .Add(New FbParameter("@id", idArticle))
                End With
                 'wird ausgeführt
                Try
                    Return executeUpdate(updateStockCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' exklusiver Abschnitt
            SyncLock Me
                Try
                     'Die Einfügeanfrage wird ausgeführt
                    executeUpdate(deleteAllCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
                End Try
            End SyncLock
        End Sub

        Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
             ' exklusiver Abschnitt
            SyncLock Me
                Try
                     'Die SELECT-Abfrage wird ausgeführt
                    Dim articles As IList = executeQuery(selectAllCommand)
                     'Die Liste wird zurückgegeben
                    Return articles
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Die SELECT-Abfrage wird vorbereitet
                With selectSomeCommand.Parameters
                    .Clear()
                    .Add(New FbParameter("@id", idArticle))
                End With
                 ': Die Abfrage wird ausgeführt
                Try
                     'Die Abfrage wird ausgeführt
                    Dim articles As IList = executeQuery(selectSomeCommand)
                     'Es wird geprüft, ob der Artikel gefunden wurde
                    If articles.Count = 0 Then Return Nothing
                     'Der Artikel wird zurückgegeben
                    Return CType(articles.Item(0), Article)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
             ' exklusiver Abschnitt
            SyncLock Me
                 ' Die Update-Abfrage wird vorbereitet
                With updatecommand.Parameters
                    .Clear()
                    .Add(New FbParameter("@nom", unArticle.nom))
                    .Add(New FbParameter("@prix", unArticle.prix))
                    .Add(New FbParameter("@sa", unArticle.stockactuel))
                    .Add(New FbParameter("@sm", unArticle.stockminimum))
                    .Add(New FbParameter("@id", unArticle.id))
                End With
                 ' die Abfrage wird ausgeführt
                Try
                     'Die Einfügeabfrage wird ausgeführt
                    Return executeUpdate(updatecommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
                End Try
            End SyncLock
        End Function

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' Exklusivbereich
            SyncLock Me
                 ' Die DELETE-Abfrage wird vorbereitet
                With deleteSomeCommand.Parameters
                    .Clear()
                    .Add(New FbParameter("@id", idArticle))
                End With
                 'wird ausgeführt
                Try
                     'Die Löschanfrage wird ausgeführt
                    Return executeUpdate(deleteSomeCommand)
                Catch ex As Exception
                     'Abfragefehler
                    Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Private Function executeQuery(ByVal query As FbCommand) As IList
             ' Ausführung einer Abfrage SELECT 
             ' Deklaration des Objekts, das den Zugriff auf alle Zeilen der Ergebnistabelle ermöglicht
            Dim myReader As FbDataReader = Nothing
            Try
                 ': Es wird eine Verbindung zu BDD hergestellt
                connexion.Open()
                 ': Die Abfrage wird ausgeführt
                myReader = query.ExecuteReader()
                 ': Deklaration einer Artikelliste, um diese anschließend zurückzugeben
                Dim articles As IList = New ArrayList
                Dim unArticle As Article
                While myReader.Read()
                     ': Es wird ein Artikel mit den Werten des Readers erstellt
                    unArticle = New Article
                    unArticle.id = myReader.GetInt32(0)
                    unArticle.nom = myReader.GetString(1)
                    unArticle.prix = myReader.GetDouble(2)
                    unArticle.stockactuel = myReader.GetInt32(3)
                    unArticle.stockminimum = myReader.GetInt32(4)
                     ': Der Artikel wird zur Liste hinzugefügt
                    articles.Add(unArticle)
                End While
                 'Das Ergebnis wird zurückgegeben
                Return articles
            Finally
                 ' Freigabe der Ressourcen
                If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function

        Private Function executeUpdate(ByVal updateCommand As FbCommand) As Integer
             ' Ausführung einer Aktualisierungsanfrage
            Try
                 ': Es wird eine Verbindung zur BDD hergestellt
                connexion.Open()
                 'Die Abfrage wird ausgeführt
                Return updateCommand.ExecuteNonQuery()
            Finally
                 ' Freigabe der Ressourcen
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function
    End Class

End Namespace

Der Leser wird gebeten, diesen Code im Zusammenhang mit den zuvor gemachten Anmerkungen zur Klasse [ArticlesDaoSqlServer] zu lesen.

2.5.3. Erstellung der Assembly der Schicht [dao]

Das neue Visual Studio-Projekt hat folgende Struktur:

Image

Zu beachten ist das Vorhandensein der Assembly [FirebirdSql.Data.Firebird.dll] in den Projektverweisen. Diese DLL wurde im Ordner [bin] des Projekts abgelegt. Das Projekt ist so konfiguriert, dass es eine DLL mit dem Namen [webarticles-dao.dll] generiert:

2.5.4. Nunit-Tests der Schicht [dao]

2.5.4.1. Die Testklasse NUnit

Die Nunit-Testklasse der Implementierungsklasse [ArticlesDaoFirebirdProvider] ist dieselbe wie die der Klasse [ArticlesDaoPlainODBC] (siehe Abschnitt 2.3.3.2). Wir gehen analog vor, um den Nunit-Test für die Klasse [ArticlesDaoFirebirdProvider] vorzubereiten:

  • Wir erstellen im Visual-Studio-Ordner des Projekts [dao-firebird-provider] den Ordner [tests] (rechts), indem wir den Ordner [bin] aus dem Testprojekt der Schicht [dao-odbc] (links) kopieren:
  • Im Ordner [tests] ersetzen wir die Dateien DLL und [webarticles-dao.dll] durch die Dateien DLL und [webarticles-dao.dll], die aus der Generierung des Projekts [dao-firebird-provider]
  • Wir ändern die Konfigurationsdatei [spring-config.xml], um die neue Klasse [ArticlesDaoFirebirdProvider] zu instanziieren:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!--
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
-->
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoFirebirdProvider, webarticles-dao">
        <constructor-arg index="0">
            <value>localhost</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>D:\data\serge\databases\firebird\dbarticles2.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>sysdba</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>masterkey</value>
        </constructor-arg>
    </object>
</objects>

Anmerkungen:

  • Zeile 7: Das Objekt [articlesdao] ist nun mit einer Instanz der Klasse [ArticlesDaoFirebirdProvider] verknüpft
  • Diese Klasse verfügt über einen Konstruktor mit vier Argumenten
  • der Host-Rechner von SGBD – Zeile 9
  • der Pfad zur Firebird-Datenbank – Zeile 12
  • der Benutzername des sich anmeldenden Benutzers – Zeile 15
  • sein Passwort – Zeile 18

2.5.4.2. Tests

Die Tabelle [ARTICLES] der Datenquelle wird mit den folgenden Einträgen gefüllt (verwenden Sie IBExpert):

Image

Wir sind bereit für die Tests. Mit Hilfe der Anwendung [Nunit-Gui] laden wir die Dateien DLL und [test-webarticles-dao.dll] aus dem oben genannten Ordner [tests] und führen den Test [testGetAllArticles] aus:

Image

Obwohl der Testklasse ursprünglich der Name [NUnitTestArticlesDaoArrayList] zugewiesen wurde, wird hier tatsächlich die Klasse [ArticlesDaoFirebirdProvider] getestet. Der Screenshot zeigt, dass wir die Artikel, die wir in die Tabelle [ARTICLES] eingefügt hatten, korrekt abgerufen haben. Führen wir nun alle Tests durch:

Image

Der Leser, der dieses Dokument auf dem Bildschirm betrachtet, kann sehen, dass alle Tests erfolgreich waren (grüne Farbe). Was er jedoch nicht sehen kann, ist, dass die Tests deutlich schneller abliefen als bei der Artikel-Datenbank, auf die über einen Treiber ODBC aus unserer ersten Implementierung zugegriffen wurde.

2.5.5. Integration der neuen Schicht [dao] in die Anwendung [webarticles]

Wir folgen dem bereits zweimal erläuterten Vorgehen, insbesondere in Abschnitt 2.3.4. Wir nehmen folgende Änderungen am Inhalt des Ordners [runtime] vor:

  • Im Ordner [bin] wird die DLL deralten Schicht [dao] wird durch die DLL der neuen Schicht [dao] ersetzt, die von der Klasse [ArticlesDaoFirebirdProvider] implementiert wird. Dort fügen wir außerdem die für Firebird erforderliche DLL sowie die [FirebirdSql.Data.Firebird.dll] ein:

Image

  • In [runtime] wird die Konfigurationsdatei [web.config] durch eine Datei ersetzt, die die neue Implementierungsklasse berücksichtigt:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoFirebirdProvider, webarticles-dao">
                <constructor-arg index="0">
                    <value>localhost</value>
                </constructor-arg>
                <constructor-arg index="1">
                    <value>D:\data\serge\databases\firebird\dbarticles2.gdb</value>
                </constructor-arg>
                <constructor-arg index="2">
                    <value>sysdba</value>
                </constructor-arg>
                <constructor-arg index="3">
                    <value>masterkey</value>
                </constructor-arg>
            </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

Anmerkungen:

  • In den Zeilen 14–27 wird dem Singleton [articlesDao] eine Instanz der neuen Klasse [ArticlesDaoFirebirdProvider] zugeordnet. Dies ist die einzige Änderung.

Wir sind bereit für die Tests. Wir konfigurieren den Webserver [Cassini] wie in den vorherigen Tests. Wir initialisieren die Artikeltabelle mit den folgenden Werten:

Image

Mit einem Browser rufen wir die Seiten URL und [http://localhost/webarticles/main.aspx] auf:

Image

Nun überprüfen wir den Inhalt der Tabelle [ARTICLES]:

Image

Die Artikel [crayon bille] und [ramette 50 feuilles] wurden gekauft und ihre Bestände um die gekaufte Menge verringert. Der Artikel [stylo plume] konnte nicht gekauft werden, da die angeforderte Menge den Lagerbestand überstieg. Wir laden den Leser ein, weitere Tests durchzuführen.

2.5.6. Die Implementierungsklasse [ArticlesDaoSqlMap]

2.5.6.1. Das Ibatis-Produkt SqlMap

Wir haben vier verschiedene Implementierungen der Schicht [dao] unserer Anwendung [webarticles] geschrieben. Jedes Mal konnten wir die neue Schicht [dao] in die Anwendung [webarticles] integrieren, ohne die beiden anderen Schichten [web] und [domain] neu kompilieren zu müssen. Dies wurde, wie bereits erwähnt, durch zwei architektonische Entscheidungen erreicht:

  • dem Zugriff auf die Schichten über Schnittstellen
  • die Integration der Schichten durch Spring

Wir möchten noch einen Schritt weiter gehen. Obwohl sie unterschiedlich sind, weisen unsere vier Implementierungen der Schicht [dao] auffällige Ähnlichkeiten auf. Nachdem die erste Implementierung geschrieben war, wurden die drei anderen quasi durch Kopieren und Einfügen sowie das Ersetzen bestimmter Schlüsselwörter durch andere Schlüsselwörter erstellt. Die Logik selbst wurde nicht verändert. Man kann sich fragen, ob es nicht möglich wäre, eine Implementierung zu haben, die uns von den verschiedenen Arten des Datenzugriffs unabhängig machen würde. Wir haben vier davon verwendet:

  • Zugriff über einen Treiber ODBC auf eine Datenquelle ODBC
  • direkter Zugriff auf eine SQL-Server-Datenbank
  • Zugriff über einen Ole-DB-Treiber auf eine Ole-DB-Datenquelle
  • direkter Zugriff auf eine Firebird-Datenbank

Das Ibatis-Tool SqlMap [[http://www.ibatis.com/] ermöglicht die Entwicklung von Datenzugriffsschichten, die unabhängig von der tatsächlichen Art der Datenquelle sind. Der Datenzugriff wird gewährleistet durch:

  • Konfigurationsdateien, in denen die Informationen zur Definition der Datenquelle und der darauf auszuführenden Operationen hinterlegt sind
  • einer Klassenbibliothek, die auf diesen Informationen aufbaut, um auf die Daten zuzugreifen

Das Tool Ibatis SqlMap wurde ursprünglich für die Java-Plattform entwickelt. Die Portierung auf die .NET-Plattform ist noch jung und scheint teilweise fehlerhaft zu sein (persönliche Einschätzung, die einer gründlichen Überprüfung bedürfte). Da sich das Tool jedoch auf der Java-Plattform bewährt hat, erscheint es interessant, die .NET-Version vorzustellen.

2.5.6.2. Wo findet man „ “ IBATIS und SqlMap?

Die Hauptwebsite von Firebird ist [http://www.ibatis.com/]. Die Download-Seite bietet folgende Links:

Image

Wir wählen den Link [Stable Binaries], der uns zu [SourceForge.net] führt. Den Download-Vorgang bis zum Ende durchführen. Man erhält eine ZIP-Datei mit den folgenden Dateien:

Image

In einem Visual Studio-Projekt, das Ibatis SqlMap verwendet, müssen zwei Schritte durchgeführt werden:

  • die oben genannten Dateien in den Ordner „[bin]“ des Projekts kopieren
  • dem Projekt eine Referenz zu jeder dieser Dateien hinzufügen

2.5.6.3. Die Konfigurationsdateien von Ibatis SqlMap

Eine Datenquelle [SqlMap] wird mithilfe der folgenden Konfigurationsdateien definiert:

  1. providers.config: Definiert die Klassenbibliotheken, die für den Zugriff auf die Daten verwendet werden sollen
  2. sqlmap.config: definiert die Eigenschaften der herzustellenden Verbindung
  3. Mapping-Dateien: Definieren die auf die Daten anzuwendenden Operationen

Die Logik dieser Dateien ist wie folgt:

  • Um auf die Daten zugreifen zu können, benötigen wir eine Verbindung. Um diese darzustellen, sind wir bereits auf mehrere Klassen gestoßen: OdbcConnection, SqlConnection, OleDbConnection, FbConnection. Außerdem benötigen wir ein Objekt vom Typ [Command], um die folgenden Abfragen zu senden: SQL: OdbcCommand, SqlCommand, OleDbCommand, FbCommand. usw. In der Datei [providers.config] definieren wir alle Klassen, die wir benötigen.
  • Die Datei [sqlmap.config] definiert im Wesentlichen die Verbindungszeichenfolge zur Datenbank, die die Daten enthält. Die Verbindung zur Datenbank wird durch Instanziierung der in [providers.config] definierten Klasse [Connection] hergestellt, deren Konstruktor die in [sqlmap.config] definierte Verbindungszeichenfolge übergeben wird.
  • Die Mapping-Dateien definieren:
    • Zuordnungen zwischen Zeilen der Datentabellen und der Klasse .NET, deren Instanzen diese Zeilen enthalten werden
    • die auszuführenden Operationen SQL. Diese werden durch einen Namen identifiziert. Der Code .NET führt diese Operationen anhand ihrer Namen aus, wodurch jeglicher SQL-Code aus dem Code .NET entfernt wird.

2.5.6.4. Die Konfigurationsdateien des Projekts [dao-sqlmap]

Betrachten wir anhand eines Beispiels die genaue Beschaffenheit der Konfigurationsdateien von SqlMap. Wir gehen dabei von dem Fall aus, dass die Datenquelle die Firebird-Quelle ODBC aus Abschnitt 2.3.3.1 ist.

2.5.6.4.1. providers.config

Die Datei [providers.config] für eine Quelle ODBC sieht wie folgt aus:

<?xml version="1.0" encoding="utf-8" ?> 

<providers>
    <clear/>
    <provider 
        name="Odbc1.1" 
        enabled="true" 
        assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
        connectionClass="System.Data.Odbc.OdbcConnection" 
        commandClass="System.Data.Odbc.OdbcCommand" 
        parameterClass="System.Data.Odbc.OdbcParameter" 
        parameterDbTypeClass="System.Data.Odbc.OdbcType" 
        parameterDbTypeProperty="OdbcType" 
        dataAdapterClass="System.Data.Odbc.OdbcDataAdapter" 
        commandBuilderClass="System.Data.Odbc.OdbcCommandBuilder" 
        usePositionalParameters = "true"
        useParameterPrefixInSql = "false"
        useParameterPrefixInParameter = "false"
        parameterPrefix = "@"
    />
</providers>

Anmerkungen:

  • Eine Datei [providers.config] wird mit dem Paket [SqlMap] ausgeliefert. Sie enthält mehrere Standard-Provider. Der obige Code stammt direkt aus dieser Datei.
  • Ein <provider> hat einen Namen – Zeile 6 – der beliebig sein kann
  • Ein <provider> kann aktiviert sein ([enabled=true]) oder nicht ([enabled=false]). Ist er aktiviert, muss die in Zeile 8 referenzierte DLL zugänglich sein. Eine Datei [providers.config] kann mehrere <provider>-Tags enthalten.
  • Zeile 8 – Name der Assembly, die die in den Zeilen 9–15 definierten Klassen enthält
  • Zeile 9 – Klasse, die zum Herstellen einer Verbindung verwendet werden soll
  • Zeile 10 – Klasse, die zum Erstellen eines Objekts [Command] zum Senden von Befehlen SQL verwendet wird
  • Zeile 11 – Klasse zur Verwaltung der Parameter eines konfigurierten Befehls SQL
  • Zeile 12 – Klasse zur Aufzählung der möglichen Datentypen für die Felder einer Tabelle
  • Zeile 13 – Name der Eigenschaft eines Objekts vom Typ [Parameter], die den Typ des Werts dieses Parameters enthält
  • Zeile 14 – Name der Klasse [Adapter], mit der Objekte vom Typ [DataSet] aus der Datenquelle erstellt werden können
  • Zeile 15 – Name der Klasse [CommandBuilder], die in Verbindung mit einem Objekt [Adapter] die automatische Generierung der Eigenschaften [InsertCommand, DeleteCommand, UpdateCommand] dieses Objekts aus dessen Eigenschaft [SelectCommand] ermöglicht
  • Zeilen 16–19 – Hier wird festgelegt, wie die parametrisierten Befehle SQL verwaltet werden. Je nach Fall muss beispielsweise Folgendes eingegeben werden:
insert into ARTICLES(id,nom,prix,stockactuel,stockminimum) values (?,?,?,?,?)

oder

insert into ARTICLES(id,nom,prix,stockactuel,stockminimum) values (@id,@nom,@prix,@sa,@sm)

Im ersten Fall handelt es sich um formale Positionsparameter. Die tatsächlichen Werte dieser Parameter müssen in der Reihenfolge der formalen Parameter angegeben werden. Im zweiten Fall handelt es sich um benannte Parameter. Einem solchen Parameter wird ein Wert zugewiesen, indem sein Name angegeben wird. Die Reihenfolge spielt dabei keine Rolle mehr.

  • Zeile 16 – Hier wird angegeben, dass die Quellen ODBC Positionsparameter verwenden
  • Zeilen 17–19 – beziehen sich auf benannte Parameter. Hier gibt es keine.

Anhand dieser Informationen weiß SqlMap beispielsweise, welche Klasse es instanziieren muss, um eine Verbindung herzustellen. In diesem Fall ist dies die Klasse [OdbcConnection] (Zeile 9).

2.5.6.4.2. sqlmap.config

Die Datei [providers.config] definiert die Klassen, die für den Zugriff auf eine Quelle ODBC verwendet werden sollen. Sie gibt keine Quelle ODBC an. Dies übernimmt die Datei [sqlmap.config]:

<?xml version="1.0" encoding="utf-8" ?>
<sqlMapConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Schemas\SqlMapConfig.xsd">
    <properties resource="properties.xml"/>
    <settings>
        <setting useStatementNamespaces="false" />
        <setting cacheModelsEnabled="false" />
    </settings>
     <!-- ==== Datenquelle ========= -->    
    <database>
        <provider name="${provider}"/>
        <dataSource name="sqlmaparticles"  connectionString="${connectionString}"/>
        <transactionManager type="ADO/SWC" />
    </database>
    <sqlMaps>
        <sqlMap resource="articles.xml" />
    </sqlMaps>
</sqlMapConfig>

Anmerkungen:

  • Zeile 3 – Es wird eine Eigenschaftsdatei [properties.xml] definiert. Diese definiert (Schlüssel, Wert)-Paare. Die Schlüssel können beliebig sein. Der einem Schlüssel C zugeordnete Wert wird durch die Notation ${C} in [sqlmap.config] abgerufen. Hier ist die Datei [properties.xml], die der vorherigen Datei [sqlmap.config] zugeordnet wird:
1
2
3
4
5
<?xml version="1.0" encoding="utf-8" ?>
<settings>
    <add key="provider" value="Odbc1.1" />
    <add key="connectionString" value="DSN=odbc-firebird-articles;UID=SYSDBA;PASSWORD=masterkey" />
</settings>

Zeile 3 – Der Schlüssel [provider] wird definiert. Sein Wert ist der Name des Tags <provider>, der in [providers.config] verwendet werden soll

Zeile 4 – Der Schlüssel [connectionString] ist definiert. Sein Wert ist die Verbindungszeichenfolge, die zum Herstellen einer Verbindung zur Firebird-Datenquelle ODBC verwendet werden soll.

  • Zeilen 4–7 – Konfigurationsparameter:
    • Zeile 5 – Die Abfragen SQL werden durch einen Namen identifiziert, der selbst Teil eines Namensraums sein kann. [useStatementNamespaces="false"] gibt an, dass keine Namensräume verwendet werden.
    • Zeile 6 – SqlMap verfügt über verschiedene Cache-Strategien, um Zugriffe auf die Datenquelle zu minimieren. [cacheModelsEnabled="false"] gibt an, dass keine davon verwendet wird.
  • Zeilen 9–13 – Hier werden die Eigenschaften der Datenquelle definiert:
    • Zeile 10 – Name des zu verwendenden <provider> von [providers.config]
    • Zeile 11 – Verbindungszeichenfolge zur Datenquelle
    • Zeile 12 – Transaktionsmanager. Hier haben wir ihn nicht verwendet, die Zeile jedoch beibehalten, da sie in der Standard-Distributionsdatei enthalten war.
  • Zeilen 14–16 – Liste der Dateien, die die an der Datenquelle auszuführenden Vorgänge SQL definieren.
  • Zeile 15 – definiert die Mapping-Datei [articles.xml]
2.5.6.4.3. articles.xml

Diese Datei erfüllt zwei Funktionen:

  • Definition eines Objekt-Mappings der Tabellen der Datenquelle. In den einfachsten Fällen läuft dies darauf hinaus, einer Zeile einer Tabelle eine Klasse zuzuordnen.
  • Definition parametrisierter Operationen SQL und deren Benennung.

Wir verwenden die folgende Datei „[articles.xml]“:

<?xml version="1.0" encoding="iso-8859-1" ?>
<sqlMap namespace="Articles" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="SqlMap.xsd">
     <!-- die resultMap -->
    <resultMaps>
        <resultMap id="article" class="istia.st.articles.dao.Article">
            <result property="id" column="ID" />
            <result property="nom" column="NOM" />
            <result property="prix" column="PRIX" />
            <result property="stockactuel" column="STOCKACTUEL" />
            <result property="stockminimum" column="STOCKMINIMUM" />
        </resultMap>
    </resultMaps>
     <!-- die Abfragen SQL -->
    <statements>
         <!-- Abruf aller Artikel -->
        <select id="getAllArticles" resultMap="article">
                select ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM FROM ARTICLES
            </select> 
         <!-- Löschen aller Artikel-->
        <delete id="clearAllArticles">
                delete from ARTICLES
            </delete> 
         <!-- Einfügen eines Artikels -->
        <insert id="insertArticle" parameterClass="istia.st.articles.dao.Article">
                insert into ARTICLES (id, nom, prix,stockactuel, stockminimum) values
                ( #(ID#, #Name#, #Preis#, #aktuellerBestand#, #Mindestbestand#)
            </insert>
         <!-- Löschen eines Artikels -->
        <delete id="deleteArticle" parameterClass="int">
                delete FROM ARTICLES where ID= #Wert#
            </delete>
         <!-- Änderung eines Artikels -->
        <update id="modifyArticle" parameterClass="istia.st.articles.dao.Article">
                update ARTICLES set NOM= #Name# ,PRIX= #Preis# ,STOCKACTUEL= #aktuellerBestand# ,STOCKMINIMUM= #Mindestbestand#, wobei ID= #ID#
            </update>
         <!-- Suche nach einem bestimmten Artikel -->
        <select id="getArticleById" resultMap="article" parameterClass="int">
                select ID, NOM, PRIX,    STOCKACTUEL, STOCKMINIMUM FROM ARTICLES where ID= #Wert#
            </select>
         <!-- Bestandsänderung eines Artikels -->
        <update id="changerStockArticle" parameterClass="Hashtable">
                update ARTICLES set STOCKACTUEL=(STOCKACTUEL + #Bewegung#) where ID=#id# and ((STOCKACTUEL + #Bewegung#) >=0)
      </update>
    </statements>
</sqlMap>

Anmerkungen:

  • Zeilen 4–11 – Hier wird eine Zuordnung zwischen einer Zeile der Tabelle [ARTICLES] der Datenquelle und der Klasse [istia.st.articles.dao.Article] definiert. Jeder Spalte (column) der Tabelle ist eine Eigenschaft (property) der Klasse [Article] zugeordnet. Diese Zuordnung ermöglicht es [SqlMap], das Ergebnis einer Operation SQL SELECT zu erstellen. Jede Ergebniszeile von SELECT wird gemäß den Regeln der Zuordnung in ein Objekt [Article] eingefügt.
  • Zeile 5 – Das Mapping wird mit dem Tag <resultMap> gekennzeichnet und über das Attribut [id="article"] benannt. Die zugehörige Klasse wird durch das Attribut [class="istia.st.articles.dao.Article"] angegeben.
  • Zeilen 14–44 – Es werden die benötigten Operationen SQL definiert
  • Zeilen 16–18 – Es wird eine Operation SELECT definiert, die als [getAllArticles] bezeichnet wird
    • Zeile 16 – Der Vorgang SELECT wird als [name= "getAllArticles "] bezeichnet, und das zu verwendende Mapping wird durch das Attribut [resultMap="article"] definiert. Hier wird also auf das Mapping in den Zeilen 5–11 verwiesen
    • Zeile 17 – Text des auszuführenden Befehls SQL
  • Zeilen 20–22 – Hier wird der SQL-Delete-Befehl [clearAllArticles] definiert, der dazu dient, die Artikeltabelle zu leeren.
  • Zeilen 24–27 – Hier wird der SQL-Insert-Befehl [insertArticle] definiert, der dazu dient, einen neuen Artikel in die Artikeltabelle einzufügen. Es handelt sich um eine Abfrage, die durch die Elemente (#id#, #nom#, #prix#, #stockactuel#, #stockminimum#) parametrisiert wird. Die Werte dieser fünf Elemente stammen aus einem als Parameter übergebenen Objekt [Article]: [parameterClass="istia.st.articles.dao.Article"]. Das Parameterobjekt muss die Eigenschaften (id, nom, prix, stockactuel, stockminimum) enthalten, auf die der parametrisierte Befehl SQL verweist.
  • Zeilen 29–31 – Hier wird der Befehl SQL „Delete [deleteArticle]“ definiert, der dazu dient, einen Artikel mit der bekannten Nummer #value# zu löschen. Diese Nummer wird als Parameter übergeben: [parameterClass="int"]. Dies ist eine allgemeine Regel. Wenn es nur einen Parameter gibt, wird er im Text des Befehls SQL durch das Schlüsselwort #value# referenziert.
  • Zeilen 33–35 – Hier wird der SQL-Update-Befehl [modifyArticle] definiert, der dazu dient, einen Artikel zu ändern, dessen Nummer bekannt ist. Wie beim Befehl [insertArticle] stammen die fünf erforderlichen Informationen aus den Eigenschaften eines Objekts [istia.st.articles.dao.Article].
  • Zeilen 37–39 – Hier wird der SQL-Select-Befehl [getArticleById] definiert, mit dem die Zeile eines Artikels abgerufen werden kann, dessen Nummer bekannt ist.
  • Zeilen 41–43 – Hier wird der SQL-Update-Befehl [changerStockArticle] definiert, der das Feld [stockactuel] eines Artikels ändert, dessen Nummer bekannt ist. Die beiden erforderlichen Informationen, die Artikelnummer #id# und die Bestandszunahme #mouvement#, werden in einem Diktionär gefunden: [parameterClass="Hashtable"]. Dieses muss zwei Schlüssel enthalten: id und mouvement. Die diesen beiden Schlüsseln zugeordneten Werte werden im Befehl SQL verwendet.
2.5.6.4.4. Speicherort der Konfigurationsdateien

Wir betrachten zwei verschiedene Situationen:

  • Im Falle eines Nunit-Tests werden die Konfigurationsdateien von [SqlMap] im selben Ordner wie die getesteten Binärdateien abgelegt.
  • Im Fall einer Webanwendung werden sie im Stammverzeichnis der Anwendung abgelegt.

2.5.6.5. API von SqlMap

Die Klassen von SqlMap sind in DLL enthalten, das in der Regel im Ordner [bin] der Anwendung abgelegt wird:

Image

Anwendungen, die die Klassen von SqlMap verwenden, müssen den Namensraum [IBatisNet.DataMapper] importieren:

Imports IBatisNet.DataMapper

Alle Operationen von SQL werden über ein Singleton vom Typ [Mapper] ausgeführt, einer Klasse aus dem Namensraum [IBatisNet.DataMapper ]. Das Singleton wird wie folgt abgerufen:

        Dim mappeur As SqlMapper = Mapper.Instance

Um den Befehl SqlMap [getAllArticles] auszuführen, schreibt man:

                    dim articles as IList=mappeur.QueryForList("getAllArticles", Nothing)
  • Mit der Methode [QueryForList] lässt sich das Ergebnis eines Befehls SELECT in einer Liste abrufen
  • Der erste Parameter ist der Name des auszuführenden Befehls SQL (siehe articles.xml)
  • Der zweite Parameter ist der Parameter, der an die Abfrage SQL übergeben werden soll. Er muss mit dem Attribut [parameterClass] des Befehls SqlMap übereinstimmen. In [articles.xml] lautet dieser [parameterClass=Nothing]. Daher wird hier ein Nullzeiger übergeben.
  • Das Ergebnis ist vom Typ IList. Die Objekte dieser Liste werden durch das Attribut [resultMap] des SQL-SELECT-Befehls angegeben: [resultMap="article"]. „article“ ist ein Mapping-Name:
<resultMap id="article" class="istia.st.articles.dao.Article">

Die diesem Mapping zugeordnete Klasse ist [istia.st.articles.dao.Article]. Letztendlich ist die oben definierte Variable [articles] eine Liste von Objekten vom Typ [ istia.st.articles.dao.Article]. Wir haben also die gesamte Tabelle [ARTICLES] mit einer einzigen Anweisung abgerufen. Ist die Tabelle [ARTICLES] leer, erhält man ein Objekt [IList] mit 0 Elementen.

Um den Befehl SqlMap [getArticleById] auszuführen, schreibt man:

dim unArticle as Article=CType(mappeur.QueryForObject("getArticleById", idArticle), Article)
  • Mit der Methode [QueryForObject] lässt sich das Ergebnis eines Befehls SELECT abrufen, der nur eine Zeile zurückgibt
  • Der erste Parameter ist der Name des auszuführenden Befehls SqlMap
  • Der zweite Parameter ist der Parameter, der an die Abfrage SQL übergeben werden soll. Er muss mit dem Attribut [parameterClass] des Befehls SqlMap übereinstimmen. In [articles.xml] steht [parameterClass="int"]. Daher übergeben wir hier eine Ganzzahl, die die Nummer des gesuchten Artikels darstellt.
  • Das Ergebnis ist vom Typ „Object“. Wenn der Befehl SELECT keine Zeilen zurückgegeben hat, ist das Ergebnis der Zeiger „null“ (nothing).

Um den Befehl SqlMap [insertArticle] auszuführen, schreibt man:

                    mappeur.Insert("insertArticle", unArticle)
  • Mit der Methode [Insert] lassen sich die Befehle SQL und INSERT ausführen
  • Der erste Parameter ist der Name des auszuführenden Befehls SqlMap
  • Der zweite Parameter ist der an diesen Befehl zu übergebende Parameter. Er muss mit dem Attribut [parameterClass] des Befehls SqlMap übereinstimmen. In [articles.xml] lautet dieser [parameterClass="istia.st.articles.dao.Article"]. Daher wird hier ein Objekt vom Typ [istia.st.articles.dao.Article] übergeben.

Um den Befehl SqlMap [deleteArticle] auszuführen, schreibt man:

                    dim nbArticles as Integer=mappeur.Delete("deleteArticle", idArticle)
  • Mit der Methode [Delete] lassen sich die Befehle SQL und DELETE ausführen
  • Der erste Parameter ist der Name des auszuführenden Befehls SQL
  • Der zweite Parameter ist der an diesen Befehl zu übergebende Parameter. Er muss mit dem Attribut [parameterClass] des Befehls SqlMap übereinstimmen. In [articles.xml] lautet dieser [parameterClass="int"]. Daher wird hier die Nummer des zu löschenden Artikels übergeben.
  • Das Ergebnis der Methode [Delete] ist die Anzahl der gelöschten Zeilen

Analog dazu schreibt man zur Ausführung des Befehls SqlMap [clearAllArticles] Folgendes:

                    dim nbArticles as Integer=mappeur.Delete("clearAllArticles", nothing)

Um den Befehl SqlMap [modifyArticle] auszuführen, gibt man Folgendes ein:

                    dim nbArticles as Integer=mappeur.Update("modifyArticle", unArticle)
  • Mit der Methode [Update] lassen sich die Befehle SQL und UPDATE ausführen
  • Der erste Parameter ist der Name des auszuführenden Befehls SqlMap
  • Der zweite Parameter ist der an diesen Befehl zu übergebende Parameter. Er muss mit dem Attribut [parameterClass] des Befehls SqlMap übereinstimmen. In [articles.xml] lautet dieser [parameterClass="istia.st.articles.dao.Article"]. Daher wird hier ein Objekt vom Typ [istia.st.articles.dao.Article] übergeben.
  • Das Ergebnis der Methode [Update] ist die Anzahl der geänderten Zeilen.

Analog dazu schreibt man zur Ausführung des Befehls SqlMap [changerStockArticle] Folgendes:

                    Dim paramètres As New Hashtable(2)
                    paramètres("id") = idArticle
                    paramètres("mouvement") = mouvement
                     ' Aktualisierung
                    dim nbLignes as Integer= mappeur.Update("changerStockArticle", paramètres)
  • Der zweite Parameter entspricht dem Attribut [parameterClass] des Befehls SqlMap. In [articles.xml] lautet er [parameterClass="Hashtable"]. Der Befehl SQL, der auf [changerStockArticle] parametriert ist, verwendet die Parameter von [id, mouvement]. Daher wird hier ein Dictionary mit diesen beiden Schlüsseln übergeben.

2.5.6.6. Der Code der Klasse [ArticlesDaoSqlMap]

Nach den vorangegangenen Erläuterungen sind wir nun in der Lage, die folgende neue Implementierungsklasse [ArticlesDaoSqlMap] zu schreiben:

Option Explicit On 
Option Strict On

Imports System
Imports IBatisNet.DataMapper
Imports System.Collections

Namespace istia.st.articles.dao

    Public Class ArticlesDaoSqlMap
        Implements IArticlesDao

         ' private Felder
        Dim mappeur As SqlMapper = Mapper.Instance

         ' Liste aller Artikel
        Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
            SyncLock Me
                Try
                    Return mappeur.QueryForList("getAllArticles", Nothing)
                Catch ex As Exception
                    Throw New Exception("Echec de l'obtention de tous les articles : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' Artikel hinzufügen
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
            SyncLock Me
                Try
                     ' unArticle: Artikel zum Hinzufügen
                     ' Einfügen
                    mappeur.Insert("insertArticle", unArticle)
                    Return 1
                Catch ex As Exception
                    Throw New Exception("Echec de l'ajout de l'article [" + unArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' Artikel löschen
        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
            SyncLock Me
                Try
                     ' ID: ID des zu löschenden Artikels
                     ' Löschen
                    Return mappeur.Delete("deleteArticle", idArticle)
                Catch ex As Exception
                    Throw New Exception("Erreur lors de la suppression de l'article d'id [" + idArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' Artikel ändern
        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
            SyncLock Me
                Try
                     ' Aktualisierung
                    Return mappeur.Update("modifyArticle", unArticle)
                Catch ex As Exception
                    Throw New Exception("Erreur lors de la mise à jour de l'article [" + unArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' Suche nach einem Artikel
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
            SyncLock Me
                Try
                     ' ID: ID des gesuchten Artikels
                    Return CType(mappeur.QueryForObject("getArticleById", idArticle), Article)
                Catch ex As Exception
                    Throw New Exception("Erreur lors de la recherche de l'article d'id [" + idArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' Löschen aller Artikel
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
            SyncLock Me
                Try
                    mappeur.Delete("clearAllArticles", Nothing)
                Catch ex As Exception
                    Throw New Exception("Erreur lors de l'effacement de la table des articles : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Sub

         ' Bestand eines Artikels ändern
        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
            SyncLock Me
                Try
                     ' ID: ID des Artikels, dessen Lagerbestand geändert wird
                     ' Bewegung: Bestandsbewegung
                    Dim paramètres As New Hashtable(2)
                    paramètres("id") = idArticle
                    paramètres("mouvement") = mouvement
                     ' Aktualisierung
                    Return mappeur.Update("changerStockArticle", paramètres)
                Catch ex As Exception
                    Throw New Exception(String.Format("Erreur lors du changement de stock [{0},{1}] : {2}", idArticle, mouvement, ex.ToString))
                End Try
            End SyncLock
        End Function
    End Class
End Namespace

Der Leser wird gebeten, diesen Code im Lichte der Erläuterungen zu API und SqlMap zu lesen. Interessanterweise hat die Verwendung von [SqlMap] den Umfang des zu schreibenden Codes erheblich reduziert.

2.5.6.7. Erstellung der Assembly der Schicht [dao]

Das neue Visual Studio-Projekt hat folgende Struktur:

Image

Zu beachten ist das Vorhandensein der für SqlMap erforderlichen „Assemblies“ in den Projektverweisen. Diese DLL wurden im Ordner [bin] des Projekts abgelegt. Das Projekt ist so konfiguriert, dass es eine DLL mit dem Namen [webarticles-dao.dll] generiert:

2.5.6.8. Nunit-Tests der Schicht [dao]

2.5.6.8.1. Die Testklasse NUnit

Die Nunit-Testklasse der Implementierungsklasse [ArticlesDaoSqlMap] ist dieselbe wie die der Klasse [ArticlesDaoPlainODBC] (siehe Abschnitt 2.3.3.2). Wir gehen analog vor, um den Nunit-Test für die Klasse [ArticlesDaoSqlMap] vorzubereiten:

  • Wir erstellen im Visual-Studio-Ordner des Projekts [dao-sqlmap] den Ordner [test1] (rechts), indem wir den Ordner [tests] aus dem Projekt [dao-odbc] (links) kopieren:
  • Im Ordner [tests] ersetzen wir die Dateien DLL und [webarticles-dao.dll] durch die Dateien DLL und [webarticles-dao.dll], die aus der Generierung des Projekts [dao-sqlmap].
  • Wir fügen die für SqlMap erforderlichen DLL sowie die untersuchten Konfigurationsdateien [providers.config, sqlmap.config, properties.xml, articles.xml] hinzu.
  • Wir ändern die Konfigurationsdatei [spring-config.xml], um die neue Klasse [ArticlesDaoSqlMap] zu instanziieren:
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="iso-8859-1" ?>
<!--
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
-->
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoSqlMap, webarticles-dao"/>
</objects>

Anmerkungen:

  • Zeile 7: Das Objekt [articlesdao] ist nun mit einer Instanz der Klasse [ArticlesDaoSqlMap] verknüpft
  • Diese Klasse verfügt über keinen Konstruktor. Es wird der Standardkonstruktor verwendet.
2.5.6.8.2. Tests

Die Tabelle [ARTICLES] der Firebird-Datenquelle wird mit den folgenden Einträgen gefüllt:

Image

Wir sind bereit für die Tests. Mit Hilfe der Anwendung [Nunit-Gui] laden wir die Dateien DLL und [test-webarticles-dao.dll] aus dem oben genannten Ordner [test1] und führen den Test [testGetAllArticles] aus:

Image

Obwohl der Testklasse ursprünglich der Name [NUnitTestArticlesDaoArrayList] zugewiesen wurde, wird hier tatsächlich die Klasse [ArticlesDaoSqlMap] getestet. Der Screenshot zeigt, dass wir die Artikel, die wir in die Tabelle [ARTICLES] eingefügt hatten, korrekt abgerufen haben. Führen wir nun alle Tests durch:

Image

Der Leser, der dieses Dokument auf dem Bildschirm betrachtet, wird sehen, dass einige Tests erfolgreich waren (grüne Farbe), andere jedoch fehlgeschlagen sind (rote Farbe). Die fehlgeschlagenen Tests sind die Tests [testArticleAbsent] und [testChangerStockArticle]. Nach umfangreichen Recherchen scheinen die Ursachen für diese Fehler folgende zu sein:

  • In [testArticleAbsent] wird die Änderung eines Artikels angefordert, der nicht existiert. Dazu wird die Methode [modifieArticle] verwendet, die die Anzahl der geänderten Zeilen auf 0 oder 1 setzt. Hier müsste der Wert 0 sein. Stattdessen tritt eine Ausnahme vom Typ [IBatisNet.Common.Exceptions.ConcurrentException] auf.
  • In [changerStockArticle] liegt erneut eine Operation vom Typ [update] vor. Dabei soll ein Bestand um eine Menge verringert werden, die größer ist als der Bestand selbst. Dazu wird die Methode [changerStockArticle] verwendet, die die Anzahl der geänderten Zeilen angibt, also 0 oder 1. Der Befehl SQL wurde geschrieben, um eine Aktualisierung zu vermeiden (siehe Befehl SQL „changerStockArticle“ in articles.xml), die den Bestand negativ machen würde. Hier wird erwartet, dass die Methode [changerStockArticle] das Ergebnis 0 liefert. Erneut tritt eine Ausnahme vom Typ [IBatisNet.Common.Exceptions.ConcurrentException] auf.

Mögliche Fehlerquellen sind zahlreich:

  1. Der Code der Klasse [ArticlesDaoSqlMap] ist fehlerhaft. Das ist möglich. Er stammt jedoch aus einer Portierung einer Java-Klasse, die mit der Java-Version von SqlMap korrekt funktioniert hatte.
  2. Die Version .NET von SqlMap ist fehlerhaft
  3. Der Firebird-Treiber ODBC ist fehlerhaft
  4. ...

Da keine Gewissheit besteht, werden wir das Problem umgehen, indem wir die besagte Ausnahme [IBatisNet.Common.Exceptions.ConcurrentException] abfangen. Der neue Code der Klasse [ArticlesDaoSqlMap] lautet nun wie folgt:

....
Namespace istia.st.articles.dao

    Public Class ArticlesDaoSqlMap
        Implements IArticlesDao

         ' private Felder
        Dim mappeur As SqlMapper = Mapper.Instance

         ' Liste aller Artikel
        Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
...
        End Function

         ' Hinzufügen eines Artikels
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
...
        End Function

         ' Artikel löschen
        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
            SyncLock Me
                Try
                     ' ID: ID des zu löschenden Artikels
                     ' Löschen
                    Return mappeur.Delete("deleteArticle", idArticle)
                Catch ex As Exception
                    If ex.GetType.Equals(GetType(IBatisNet.Common.Exceptions.ConcurrentException)) Then Return 0
                    Throw New Exception("Erreur lors de la suppression de l'article d'id [" + idArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' Artikel bearbeiten
        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
            SyncLock Me
                Try
                     ' Aktualisierung
                    Return mappeur.Update("modifyArticle", unArticle)
                Catch ex As Exception
                    If ex.GetType.Equals(GetType(IBatisNet.Common.Exceptions.ConcurrentException)) Then Return 0
                    Throw New Exception("Erreur lors de la mise à jour de l'article [" + unArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' Suche nach einem Artikel
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
...
        End Function

         ' Löschen aller Artikel
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
....
        End Sub

         ' Bestand eines Artikels ändern
        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
            SyncLock Me
                Try
                     ' ID: ID des Artikels, dessen Bestand geändert wird
                     ' Bewegung: Bestandsbewegung
                    Dim paramètres As New Hashtable(2)
                    paramètres("id") = idArticle
                    paramètres("mouvement") = mouvement
                     ' Aktualisierung
                    Return mappeur.Update("changerStockArticle", paramètres)
                Catch ex As Exception
                    If ex.GetType.Equals(GetType(IBatisNet.Common.Exceptions.ConcurrentException)) Then Return 0
                    Throw New Exception(String.Format("Erreur lors du changement de stock [{0},{1}] : {2}", idArticle, mouvement, ex.ToString))
                End Try
            End SyncLock
        End Function
    End Class
End Namespace

Die Änderungen befinden sich in den Zeilen: 28, 41, 69. Bei Vorgängen vom Typ SQL, die dem Typ [UPDATE, DELETE] entsprechen, wird im Falle einer Ausnahme vom Typ [IBatisNet.Common.Exceptions.ConcurrentException] der Wert 0 als Ergebnis zurückgegeben, um damit anzuzeigen, dass keine Zeile geändert oder gelöscht wurde. Anschließend wird die DLL des Projekts neu generiert, im Ordner [test1] abgelegt und die Tests NUnit erneut gestartet:

Image

Diesmal klappt es. Wir werden nun mit dieser DLL arbeiten.

2.5.6.9. Integration der neuen Ebene [dao] in die Anwendung [webarticles]

2.5.6.9.1. Datenquelle ODBC

Wir testen hier die in Abschnitt 2.3.3.1 behandelte Datenquelle ODBC. Sie wird hier über SqlMap verwendet.

Wir folgen der Vorgehensweise aus Abschnitt 2.3.4. Wir nehmen folgende Änderungen am Inhalt des Ordners [runtime] vor:

  • Im Ordner [bin] wird die Datei DLL deralten Schicht [dao] wird durch die DLL der neuen Schicht [dao] ersetzt, die von der Klasse [ArticlesDaoSqlMap] implementiert wird. Wir fügen die für Firebird erforderlichen DLL sowie SqlMap hinzu:

Image

  • In [runtime] werden die Konfigurationsdateien von SqlMap und [providers.config, sqlmap.config, properties.xml, articles.xml] abgelegt:

Image

  • In [runtime] wird die Konfigurationsdatei [web.config] durch eine Datei ersetzt, die die neue Implementierungsklasse berücksichtigt:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoSqlMap, webarticles-dao"/>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

Anmerkungen:

  • In Zeile 14 wird dem Singleton [articlesDao] eine Instanz der neuen Klasse [ArticlesDaoSqlMap] zugeordnet. Dies ist die einzige Änderung.

Wir sind bereit für die Tests. Wir konfigurieren den Webserver [Cassini] wie in den vorherigen Tests. Wir initialisieren die Artikeltabelle mit den folgenden Werten:

Image

Mit einem Browser rufen wir die Seiten URL und [http://localhost/webarticles/main.aspx] auf:

Image

Nun überprüfen wir den Inhalt der Tabelle [ARTICLES]:

Image

Die Artikel [couteau] und [cuiller] wurden gekauft und ihre Bestände um die gekaufte Menge verringert. Der Artikel [fourchette] konnte nicht gekauft werden, da die angeforderte Menge den Lagerbestand überstieg. Wir laden den Leser ein, weitere Tests durchzuführen.

2.5.6.9.2. Datenquelle MSDE

Wir testen hier die in Abschnitt 2.4.3.1 behandelte Datenquelle MSDE. Sie wird hier über SqlMap verwendet. Wir gehen dabei genauso vor wie zuvor. Wir nehmen folgende Änderungen am Inhalt des Ordners [runtime] vor:

  • Der Inhalt des Ordners [bin] bleibt unverändert
  • In [runtime] ändern sich die Konfigurationsdateien von SqlMap und [providers.config, properties.xml]. Die Konfigurationsdateien von [sqlmap.config, articles.xml] bleiben unverändert.
  • Die Datei [providers.config] konfiguriert einen neuen <provider>:
<?xml version="1.0" encoding="utf-8" ?> 

<providers>
    <clear/>
    <provider
        name="sqlServer1.1"
        assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
        connectionClass="System.Data.SqlClient.SqlConnection"
        commandClass="System.Data.SqlClient.SqlCommand"
        parameterClass="System.Data.SqlClient.SqlParameter"
        parameterDbTypeClass="System.Data.SqlDbType"
        parameterDbTypeProperty="SqlDbType"
        dataAdapterClass="System.Data.SqlClient.SqlDataAdapter"
        commandBuilderClass="System.Data.SqlClient.SqlCommandBuilder"    
        usePositionalParameters = "false"    
        useParameterPrefixInSql = "true"
        useParameterPrefixInParameter = "true"                
        parameterPrefix="@"
    />        
</providers>

Dieser <provider> verwendet die Klassen .NET für den Zugriff auf die Datenquellen des SQL-Servers. Er ist standardmäßig in die Vorlagendatei [providers.config] integriert, die zusammen mit SqlMap ausgeliefert wird.

  • Die Datei [properties.xml] definiert den <provider> der Quelle MSDE sowie deren Verbindungszeichenfolge:
<?xml version="1.0" encoding="utf-8" ?> 
<settings>
    <add key="provider" value="sqlServer1.1" />
    <add 
        key="connectionString" 
        value="Data Source=portable1_tahe\msde140405;Initial Catalog=dbarticles;UID=admarticles;PASSWORD=mdparticles;"/>
</settings>
  • In [runtime] bleibt die Konfigurationsdatei [web.config] unverändert.

Wir sind bereit für die Tests. Der Webserver [Cassini] behält seine übliche Konfiguration bei. Wir initialisieren die Artikeltabelle der Quelle MSDE mit [EMS MS SQL Manager]:

Image

Mit einem Browser rufen wir die Seiten URL und [http://localhost/webarticles/main.aspx] auf:

Image

Nun überprüfen wir den Inhalt der Tabelle [ARTICLES] mit [EMS MS SQL Manager]:

Image

Die Artikel [ballon foot] und [raquette tennis] wurden gekauft und ihre Bestände um die gekaufte Menge verringert. Der Artikel [rollers] konnte nicht gekauft werden, da die angeforderte Menge den Lagerbestand überstieg. Wir laden den Leser ein, weitere Tests durchzuführen.

2.5.6.9.3. Datenquelle OleDb

Wir testen hier die in Abschnitt 2.4.5.1 vorgestellte Datenquelle ACCESS. Sie wird hier über SqlMap verwendet. Wir gehen dabei genauso vor wie zuvor. Wir nehmen folgende Änderungen am Inhalt des Ordners [runtime] vor:

  • Der Inhalt des Ordners [bin] bleibt unverändert
  • In [runtime] ändern sich die Konfigurationsdateien von SqlMap und [providers.config, properties.xml]. Die Konfigurationsdateien von [sqlmap.config, articles.xml] bleiben unverändert.
  • Die Datei „[providers.config]“ konfiguriert einen neuen <provider>:
<?xml version="1.0" encoding="utf-8" ?> 

<providers>
    <clear/>
    <provider 
        name="OleDb1.1" 
        enabled="true" 
        assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
        connectionClass="System.Data.OleDb.OleDbConnection" 
        commandClass="System.Data.OleDb.OleDbCommand" 
        parameterClass="System.Data.OleDb.OleDbParameter" 
        parameterDbTypeClass="System.Data.OleDb.OleDbType" 
        parameterDbTypeProperty="OleDbType" 
        dataAdapterClass="System.Data.OleDb.OleDbDataAdapter" 
        commandBuilderClass="System.Data.OleDb.OleDbCommandBuilder" 
        usePositionalParameters = "true"
        useParameterPrefixInSql = "false"
        useParameterPrefixInParameter = "false"
        parameterPrefix = ""
    />
</providers>

Dieser <provider> verwendet die Klassen .NET für den Zugriff auf die Datenquellen OleDb. Er ist standardmäßig in die Vorlagendatei [providers.config] integriert, die zusammen mit SqlMap ausgeliefert wird.

  • Die Datei [properties.xml] definiert den <provider> der Quelle OleDb sowie deren Verbindungszeichenfolge:
<?xml version="1.0" encoding="utf-8" ?> 
<settings>
    <add key="provider" value="OleDb1.1" />
    <add 
        key="connectionString" 
        value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;"/>
</settings>
  • In [runtime] bleibt die Konfigurationsdatei [web.config] unverändert.

Wir sind bereit für die Tests. Der Webserver [Cassini] behält seine übliche Konfiguration bei. Wir initialisieren die Artikeltabelle der Quelle ACCESS wie folgt:

Image

Mit einem Browser rufen wir die Seiten URL und [http://localhost/webarticles/main.aspx] auf:

Image

Nun überprüfen wir den Inhalt der Tabelle [ARTICLES] mit:

Image

Die Artikel [pantalon] und [jupe] wurden gekauft und ihre Bestände um die gekaufte Menge verringert. Der Artikel [manteau] konnte nicht gekauft werden, da die angeforderte Menge den Lagerbestand überstieg. Wir laden den Leser ein, weitere Tests durchzuführen.

2.5.7. Fazit

Hiermit beenden wir diesen langen Tutorial-Artikel. Was haben wir getan?

  • Wir haben die Schicht [dao] einer dreischichtigen Webanwendung auf vier verschiedene Arten implementiert:
    1. unter Verwendung der Zugriffsklassen .NET auf die Quellen ODBC
    2. unter Verwendung der Zugriffsklassen .NET für die SQL-Server-Quellen
    3. unter Verwendung der Zugriffsklassen .NET auf die Quellen OleDb
    4. unter Verwendung von Zugriffsklassen eines Drittanbieters für den Zugriff auf eine Firebird-Datenbank
  • Jedes Mal haben wir die neue Schicht [dao] in die dreischichtige Anwendung [webarticles] integriert, ohne eine der Schichten [web, domain, dao] oder [web, domain] neu zu kompilieren
  • Schließlich haben wir das Tool [SqlMap] vorgestellt, mit dem wir eine Schicht [dao] erstellen konnten, die sich für den Code transparent an verschiedene Datenquellen anpassen lässt. So konnten wir mit dieser neuen Schicht nacheinander die Datenquellen der vorherigen Implementierungen 1 bis 3 nutzen. Dies erfolgte transparent mithilfe von Konfigurationsdateien.
  • Wir haben gezeigt, welche große Flexibilität die Tools Spring und SqlMap für dreischichtige Webanwendungen bieten.