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

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:
- 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.
- 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.
- 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
- 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.
- 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:
- die Geschäftsklassen
- die Datenzugriffsklassen
- 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);
Primärschlüssel, der einen Artikel eindeutig identifiziert | |
Name des Artikels | |
sein Preis | |
Aktueller Lagerbestand | |
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:

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

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:
- einen Konstruktor, mit dem die 5 Informationen eines Artikels festgelegt werden können: [id, nom, prix, stockactuel, stockminimum]
- öffentliche Eigenschaften zum Lesen und Schreiben der 5 Informationen.
- eine Überprüfung der in den Artikel eingegebenen Daten. Bei fehlerhaften Daten wird eine Ausnahme ausgelöst.
- 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:
gibt alle Artikel aus der Datenquelle zurück | |
leert die Datenquelle | |
gibt das durch seine Nummer identifizierte Objekt [Article] zurück | |
ermöglicht das Hinzufügen eines Artikels zur Datenquelle | |
ermöglicht die Änderung eines Artikels in der Datenquelle | |
ermöglicht das Löschen eines Artikels aus der Datenquelle | |
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:
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
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
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
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
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
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
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
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
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
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
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:

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:

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

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:

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

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:

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

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:

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:
![]() | ![]() |

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:

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

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:

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

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:

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

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:

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

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

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

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

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

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

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

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

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

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:

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

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:

![]() |
Nun überprüfen wir den Inhalt der Tabelle [ARTICLES] mit ACCESS:

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):
![]()
Ü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:

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:

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

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:

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:

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:

- In [runtime] wird die Konfigurationsdatei [web.config] durch eine Datei ersetzt, die die neue Implementierungsklasse berücksichtigt:
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:

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

![]() |
Nun überprüfen wir den Inhalt der Tabelle [ARTICLES]:

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:

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:

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:
- providers.config: Definiert die Klassenbibliotheken, die für den Zugriff auf die Daten verwendet werden sollen
- sqlmap.config: definiert die Eigenschaften der herzustellenden Verbindung
- 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:
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:
oder
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]:
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:
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]“:
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:

Anwendungen, die die Klassen von SqlMap verwenden, müssen den Namensraum [IBatisNet.DataMapper] importieren:
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:
Um den Befehl SqlMap [getAllArticles] auszuführen, schreibt man:
- 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:
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:
- 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:
- 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:
- 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:
Um den Befehl SqlMap [modifyArticle] auszuführen, gibt man Folgendes ein:
- 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:

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

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:

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:

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:
- 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.
- Die Version .NET von SqlMap ist fehlerhaft
- Der Firebird-Treiber ODBC ist fehlerhaft
- ...
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:
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:

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:

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

- In [runtime] wird die Konfigurationsdatei [web.config] durch eine Datei ersetzt, die die neue Implementierungsklasse berücksichtigt:
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:

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

![]() |
Nun überprüfen wir den Inhalt der Tabelle [ARTICLES]:

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

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

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

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:

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

![]() |
Nun überprüfen wir den Inhalt der Tabelle [ARTICLES] mit:

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:
- unter Verwendung der Zugriffsklassen .NET auf die Quellen ODBC
- unter Verwendung der Zugriffsklassen .NET für die SQL-Server-Quellen
- unter Verwendung der Zugriffsklassen .NET auf die Quellen OleDb
- 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.































































