2. Deel 2
2.1. Introduction
We beginnen met een terugblik op wat er in deel 1 is gedaan, en met name op de gebruikte drielaagse architectuur [web, domain, dao]. In de voorgestelde oplossing was de [dao]-laag een testlaag: de gegevensbron werd geïmplementeerd door een [ArrayList]-object. In dit artikel gaan we dieper in op de [dao]-laag en presenteren we verschillende mogelijke implementaties daarvan wanneer de gegevens zich in een SGBD bevinden.
Gebruikte tools:
- het SGBD Firebird – zie bijlage paragraaf 3.5.
- de SGBD MSDE (Microsoft Data Engine) – zie bijlage paragraaf 3.12.
- IBExpert, Personal Edition voor het grafisch beheren van de SGBD Firebird – zie bijlage paragraaf 3.6.
- EMS MS SQL Manager voor het grafisch beheren van de SGBD MSDE - zie bijlage, paragraaf 3.14.
- Ibatis SqlMap voor de gegevenslaag van SGBD – zie paragraaf 2.5.6.2.
Op een schaal van beginners-gemiddeld-gevorderd bevindt dit document zich in het gedeelte [intermédiaire-avancé]. Om het te kunnen begrijpen, zijn diverse voorkennisvereisten nodig. Sommige daarvan kunnen worden opgedaan in documenten die ik heb geschreven. In dat geval vermeld ik ze. Het spreekt voor zich dat dit slechts een suggestie is en dat de lezer zijn eigen favoriete documenten kan gebruiken.
- taal VB.net: [Introduction au langage VB.NET par l'exemple ]
- webprogrammering in VB.net: [Développement WEB avec ASP.NET 1.1 ]
- gebruik van de IoC-component van Spring: [Spring IoC pour .NET ]
- Ibatis-documentatie SqlMap: [http://prdownloads.sourceforge.net/ibatisnet/DevGuide.pdf?download]
- Firebird-documentatie: [http://firebird.sourceforge.net/pdfmanual/Firebird-1.5-QuickStart.pdf]
- documentatie Spring.net: [http://www.springframework.net/documentation.html]
2.2. De webapplicatie 'articles' – Herhaling
Hier presenteren we de onderdelen van de vereenvoudigde e-commerce-webapplicatie die in deel 1 is behandeld. Hiermee kunnen webklanten:
- een lijst met artikelen uit een database te bekijken
- bepaalde artikelen in een elektronisch winkelmandje te plaatsen
- deze te bevestigen. Deze bevestiging heeft als enig effect dat de voorraden van de gekochte artikelen in de database worden bijgewerkt.
2.2.1. De weergaven van de applicatie
De verschillende weergaven die aan de gebruiker worden getoond, zijn de volgende:
![]() |
![]() |
![]() |
- de weergave [ERREURS], die eventuele fouten in de applicatie meldt

2.2.2. Algemene architectuur van de applicatie
De in deel 1 gebouwde applicatie heeft een drielaagse architectuur:
![]() |
- de drie lagen zijn onafhankelijk van elkaar gemaakt door het gebruik van interfaces
- De integratie van de verschillende lagen is gerealiseerd met Spring
- Elke laag heeft een eigen naamruimte: web (UI-laag), domain (bedrijfslaag) en dao (gegevenslaag).
De applicatie volgt een MVC-architectuur (Model - View - Controller). Als we het bovenstaande lagenmodel nog eens bekijken, past de MVC-architectuur er als volgt in:
![]() |
De verwerking van een verzoek van een klant verloopt volgens de volgende stappen:
- de klant doet een verzoek aan de controller. Deze controller is hier een .aspx-pagina die een specifieke rol vervult. Alle verzoeken van klanten komen hier langs. Dit is de toegangspoort tot de applicatie. Dit is de C van MVC.
- de controller verwerkt dit verzoek. Hiervoor kan hij de hulp nodig hebben van de businesslaag, het zogenaamde M-model in de structuur MVC.
- De controller ontvangt een antwoord van de businesslaag. Het verzoek van de klant is verwerkt. Dit kan verschillende mogelijke antwoorden opleveren. Een klassiek voorbeeld is
- een foutpagina als het verzoek niet correct kon worden verwerkt
- een bevestigingspagina in het andere geval
- de controller kiest welk antwoord (= weergave) naar de klant moet worden verzonden. Dit is meestal een pagina met dynamische elementen. De controller levert deze aan de weergave.
- De weergave wordt naar de klant verzonden. Dit is de V van MVC.
2.2.3. Het model
Het model M van MVC bestaat hier uit de volgende elementen:
- de businessklassen
- de klassen voor gegevenstoegang
- de database
2.2.3.1. De database
De database bevat slechts één tabel met de naam ARTICLES, die is gegenereerd met de volgende SQL-opdrachten:
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
);
/* beperkingen */
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);
/* primaire sleutel */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
primaire sleutel die een artikel op unieke wijze identificeert | |
naam van het artikel | |
de prijs ervan | |
huidige voorraad | |
de voorraad onder welke een herbevoorradingsorder moet worden geplaatst |
2.2.3.2. De naamruimten van het model
Het model M wordt geleverd in de vorm van twee naamruimten:
- istia.st.articles.dao: bevat de klassen voor gegevenstoegang van de laag [dao]
- istia.st.articles.domain: bevat de bedrijfsclassen van de laag [domain]
Elk van deze naamruimten is opgenomen in een eigen "assembly"-bestand:
assembly | contenu | rôle |
webarticles-dao | - [IArticlesDao]: de interface voor toegang tot de laag [dao]. Dit is de enige interface die de laag [domain] ziet. Deze ziet geen andere. - [Article]: klasse die een artikel definieert - [ArticlesDaoArrayList]: implementatieklasse van de interface [IArticlesDao] met een klasse [ArrayList] | datatoegangslaag – bevindt zich volledig in de laag [dao] van de 3-tier-architectuur van de webapplicatie |
webarticles-domain | - [IArticlesDomain]: de interface voor toegang tot de laag [domain]. Dit is de enige interface die de weblaag ziet. De weblaag ziet geen andere. - [AchatsArticles]: een klasse die [IArticlesDomain] implementeert - [Achat]: klasse die de aankoop van een klant vertegenwoordigt - [Panier]: klasse die alle aankopen van een klant vertegenwoordigt | vertegenwoordigt het model van online aankopen - bevindt zich volledig in de laag [domain] van de 3-tier-architectuur van de webapplicatie |
2.2.4. Implementatie en testen van de applicatie [webarticles]
2.2.4.1. Implementatie
We implementeren de applicatie die in deel 1 van het artikel is ontwikkeld in een map met de naam [runtime]:
![]() | ![]() |
![]() |
Opmerkingen:
De map [runtime] bevat drie bestanden en twee submappen:
- de controllers [global.asax] en [main.aspx]
- het configuratiebestand [web.config]
- de map [bin] die het volgende bevat:
- de DLL-bestanden van de drie lagen [webarticles-dao.dll], [webarticles-domain.dll] en [webarticles-web.dll]
- de benodigde bestanden voor Spring: [Spring-Core.*], [log4net.dll]
- de map [vues] die de presentatiecode van de verschillende weergaven bevat.
- De aanwezigheid van de .vb-codebestanden is overbodig, aangezien de gecompileerde versie ervan in de DLL staat.
2.2.4.2. Tests
We configureren de webserver [Cassini] als volgt:

met:
Fysiek pad: D:\data\serge\werk\2004-2005\aspnet\webarticles-010405\runtime\
Virtueel pad: /webarticles
Met een browser roepen we de pagina's URL en [http://localhost/webarticles/main.aspx] op

Ter herinnering: de laag [dao] wordt geïmplementeerd door een klasse die de artikelen opslaat in een object [ArrayList]. Deze klasse maakt een initiële lijst van vier artikelen aan. Vanuit het bovenstaande scherm gebruiken we de menu-links om bewerkingen uit te voeren. Hier volgen er enkele. De linkerkolom geeft de aanvraag van de klant weer en de rechterkolom het antwoord dat aan hem wordt gegeven.
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
2.2.5. De [dao]-laag herzien
In onze eerste implementatie van de [dao]-laag was de [IArticlesDao]-interface voor gegevenstoegang geïmplementeerd door een klasse die de artikelen opsloeg in een [ArrayList]-object. Hierdoor hoefden we niet te veel aandacht te besteden aan deze laag en konden we aantonen dat alleen de interface van belang was, en niet de implementatie ervan. Zo konden we een werkende webapplicatie bouwen. Deze bestaat uit drie lagen: [web], [domain] en [dao]. We zullen hier verschillende implementaties van de laag [dao] voorstellen. Elk van deze implementaties kan de huidige laag [dao] vervangen zonder dat er wijzigingen nodig zijn in de lagen [domain] en [web]. Deze flexibiliteit wordt bereikt omdat:
- de laag [domain] niet gericht is op een concrete klasse, maar op een interface [IArticlesDao]
- we dankzij Spring de naam van de implementatieklasse van de interface [IArticlesDao] voor de laag [domain] hebben kunnen verbergen.
2.2.5.1. Elementen van de laag [dao]
Laten we enkele elementen van de laag [dao] in herinnering brengen die in de nieuwe implementaties behouden zullen blijven:
- - [IArticlesDao]: de interface voor toegang tot de laag [dao]
- - [Article]: klasse die een artikel definieert
2.2.5.2. De klasse [Article]
De klasse die een artikel definieert, is de volgende:
Imports System
Namespace istia.st.articles.dao
Public Class Article
' privévelden
Private _id As Integer
Private _nom As String
Private _prix As Double
Private _stockactuel As Integer
Private _stockminimum As Integer
' artikel-id
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
' artikelprijs
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
' huidige voorraad artikel
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
' minimale voorraad artikel
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
' standaardfabrikant
Public Sub New()
End Sub
' fabrikant met eigenschappen
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
' methode voor artikelidentificatie
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
Deze klasse biedt:
- een constructor waarmee de 5 gegevens van een artikel kunnen worden vastgelegd: [id, nom, prix, stockactuel, stockminimum]
- openbare eigenschappen waarmee de 5 gegevens kunnen worden gelezen en geschreven.
- een controle van de gegevens die in het artikel zijn ingevoerd. Bij foutieve gegevens wordt er een uitzondering gegenereerd.
- een methode toString waarmee de waarde van een artikel als tekenreeks kan worden opgehaald. Dit is vaak handig bij het debuggen van een applicatie.
2.2.5.3. De interface [IArticlesDao]
De interface [IArticlesDao] is als volgt gedefinieerd:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' lijst van alle artikelen
Function getAllArticles() As IList
' voegt een artikel toe
Function ajouteArticle(ByVal unArticle As Article) As Integer
' verwijdert een artikel
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' een artikel wijzigen
Function modifieArticle(ByVal unArticle As Article) As Integer
' een artikel zoeken
Function getArticleById(ByVal idArticle As Integer) As Article
' verwijdert alle artikelen
Sub clearAllArticles()
' wijzigt de voorraad van een artikel
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
De verschillende methoden van de interface hebben de volgende functies:
haalt alle artikelen uit de gegevensbron op | |
maakt de gegevensbron leeg | |
geeft het object [Article] weer, geïdentificeerd aan de hand van zijn nummer | |
maakt het mogelijk een artikel aan de gegevensbron toe te voegen | |
maakt het mogelijk een artikel in de gegevensbron te wijzigen | |
hiermee kun je een artikel uit de gegevensbron verwijderen | |
maakt het mogelijk de voorraad van een artikel in de gegevensbron te wijzigen |
De interface stelt clientprogramma's een aantal methoden ter beschikking die uitsluitend door hun handtekeningen worden gedefinieerd. De interface houdt zich niet bezig met de manier waarop deze methoden daadwerkelijk worden geïmplementeerd. Dit zorgt voor flexibiliteit in een toepassing. Het clientprogramma doet zijn aanroepen op een interface en niet op een specifieke implementatie daarvan.
![]() |
De keuze voor een specifieke implementatie gebeurt via een Spring-configuratiebestand.
2.3. De implementatieklasse [ArticlesDaoPlainODBC]
We bieden een nieuwe implementatie van de laag [dao] aan, die ervan uitgaat dat de gegevens zich in een bron ODBC bevinden. Het is bekend dat onder Windows vrijwel alle SGBD-apparaten op de markt beschikken over een stuurprogramma ODBC. Het voordeel van deze oplossing is dat men van SGBD kan wisselen op een voor de applicatie transparante manier. Het nadeel is dat een stuurprogramma ODBC dat alleen gebruikmaakt van de kenmerken die alle SGBD-bestanden gemeen hebben, over het algemeen minder goed presteert dan een stuurprogramma dat specifiek is geschreven om het volledige potentieel van een bepaald SGBD-bestand te benutten. Zie paragraaf 3.7 voor een voorbeeld van het maken van een ODBC-bron.
2.3.1. De code
2.3.1.1. Het skelet
De klasse [ArticlesDaoPlainODBC] implementeert de interface [IArticlesDao] als volgt:
Opmerkingen:
- regel 3: we importeren de naamruimte met de klassen .NET voor toegang tot de bronnen ODBC
- regel 11 – slaat de verbinding met de bron ODBC op
- regel 12 – slaat de naam DSN van de gegevensbron op
- regels 13-19 – privévariabelen van het type [OdbcCommand] die de query's SQL definiëren die door de verschillende methoden van de klasse worden gebruikt
- regels 22-27 - de constructor. Deze ontvangt de elementen waarmee het object [OdbcConnection] kan worden geconstrueerd, dat de code aan de gegevensbron ODBC koppelt
- regels 29-31 – de methode voor het toevoegen van een artikel
- regels 33-35 – de methode om de voorraad van een artikel te wijzigen
- regels 37-39 - de methode die alle artikelen uit de gegevensbron ODBC verwijdert
- regels 41-43 – de methode die de lijst met alle artikelen uit de bron ODBC ophaalt
- regels 45-47 – de methode waarmee een specifiek artikel kan worden opgehaald
- regels 49-51 – de methode waarmee bepaalde velden van een artikel kunnen worden gewijzigd waarvan het nummer bekend is
- regels 53-55 - de methode waarmee een artikel kan worden verwijderd waarvan het nummer bekend is
- regels 57-60 - hulpprogramma waarmee een [SELECT] op de gegevensbron kan worden uitgevoerd en het resultaat kan worden weergegeven
- regels 62-64 - hulpprogramma waarmee een [INSERT, UPDATE, DELETE] op de gegevensbron kan worden uitgevoerd en het resultaat kan worden weergegeven
2.3.1.2. De constructor
Opmerkingen:
- regel 2 – de constructor ontvangt de drie gegevens die hij nodig heeft om verbinding te maken met een ODBC-bron: de naam DSN van de bron, de gebruikersnaam waarmee verbinding moet worden gemaakt en het bijbehorende wachtwoord.
- regel 8 - de naam DSN van de bron wordt opgeslagen om deze in foutmeldingen te kunnen weergeven.
- regel 9 - het object [OdbcConnection] wordt geïnstantieerd. Een geïnstantieerde verbinding is geen geopende verbinding. De methode [open] zorgt voor het openen ervan.
- regels 12-19 – we bereiden de query’s SQL voor in de objecten [OdbcCommand]. Zo hoeven we ze niet telkens opnieuw op te bouwen wanneer we ze nodig hebben. De formele parameters ? van de query’s worden bij de uitvoering van de query vervangen door daadwerkelijke waarden.
2.3.1.3. De methode executeQuery
Opmerkingen:
- de methode [executeQuery] is een hulpprogramma dat:
- een query [SELECT id, nom, prix, stockactuel, stockminimum from ARTICLES ...] uitvoert op de gegevensbron
- het resultaat teruggeeft in de vorm van een lijst met [Article]-objecten
- regel 1 - de enige parameter van de methode is het object [OdbcCommand] dat de uit te voeren query [Select] bevat.
- regel 7 – de verbinding is geopend. Deze wordt gesloten op regel 29, ongeacht of er een fout is opgetreden of niet.
- regel 9 - het object [OdbcDataReader], dat nodig is om het resultaat van [Select] te verwerken, wordt geïnstantieerd
- regels 13-23 – elke resultaatregel van [Select] wordt in een object [Article] geplaatst, dat zich bij de andere artikelen voegt in een object [ArrayList]
- de lijst met artikelen wordt weergegeven op regel 25
- Er worden geen uitzonderingen afgehandeld. Dit moet gebeuren door de code die deze methode aanroept.
2.3.1.4. De methode executeUpdate
Opmerkingen:
- de methode ontvangt een object [OdbcCommand] dat een verzoek SQL van het type [Insert, Update, Delete] bevat.
- De verbinding wordt geopend in regel 5. Deze wordt gesloten in regel 10, ongeacht of er een uitzondering is opgetreden of niet.
- De update-query wordt uitgevoerd op regel 7. Het resultaat wordt onmiddellijk geretourneerd; dit is het aantal rijen in de tabel ARTICLES dat door de query is gewijzigd.
2.3.1.5. De methode ajouteArticle
Opmerkingen:
- regel 1 – de methode ontvangt het artikel dat moet worden toegevoegd aan de gegevensbron ODBC. Ze retourneert het aantal rijen dat door deze bewerking wordt beïnvloed, c.a.d. 1 of 0
- regels 3 en 20 - de methode is gesynchroniseerd. Dit geldt voor alle methoden die toegang geven tot de gegevens. Dit houdt in dat er slechts één thread tegelijk aan de gegevensbron kan werken. Dit is waarschijnlijk te conservatief. Er bestaan betere alternatieven, met name het opnemen van deze bewerkingen in transacties. In dit geval is het SGBD dat de gelijktijdige toegang beheert. We wilden het concept van transacties op dit moment nog niet introduceren. Spring biedt ons de mogelijkheid om deze in de laag [domain] te introduceren. Misschien komen we hier in een ander artikel nog op terug.
- In de regels 5-12 worden waarden toegekend aan de formele parameters van de aanroep van het object [insertCommand], dat door de constructor is geïnitialiseerd. Laten we deze nog eens herhalen:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
De 5 waarden die nodig zijn voor de query worden geleverd door de regels 7-11.
- In de regels 13-19 wordt de aanvraag uitgevoerd. Als dit goed verloopt, wordt het resultaat teruggegeven. Zo niet, dan wordt er een generieke uitzondering gegenereerd met een expliciete foutmelding
2.3.1.6. De methode modifieArticle
Opmerkingen:
- regel 1 – de methode ontvangt het te wijzigen artikel uit de gegevensbron ODBC. Ze retourneert het aantal regels dat door deze bewerking wordt beïnvloed, c.a.d. 1 of 0
- de opmerkingen bij de methode [ajouteArticle] kunnen hier worden overgenomen
2.3.1.7. De methode supprimeArticle
Opmerkingen:
- regel 1 - de methode ontvangt het nummer van het artikel dat moet worden verwijderd uit de gegevensbron ODBC. Ze retourneert het aantal rijen dat door deze bewerking wordt beïnvloed, c.a.d. 1 of 0
- de opmerkingen bij de methode [ajouteArticle] kunnen hier worden overgenomen
2.3.1.8. De methode getAllArticles
Opmerkingen:
- regel 1 – de methode ontvangt geen parameters. Ze retourneert de lijst met alle artikelen uit de gegevensbron ODBC
- de query [Select], die alle artikelen opvraagt, wordt aangeroepen door de methode [executeQuery] – regel 6
- de verkregen lijst wordt weergegeven in regel 8
- in de regels 9-12 wordt een eventuele uitzondering afgehandeld
2.3.1.9. De methode getArticleById
Opmerkingen:
- regel 1 - de methode ontvangt als parameter het nummer van het gewenste artikel. Ze retourneert dit nummer als het wordt gevonden in de bron ODBC, anders retourneert ze de referentie [nothing].
- De aanvraag [Select] voor het artikel wordt geïnitialiseerd in de regels 5-8
- deze wordt uitgevoerd in regel 12 – er wordt een lijst met artikelen verkregen
- als deze lijst leeg is, wordt de referentie [nothing] weergegeven op regel 14
- anders wordt het enige artikel uit de lijst weergegeven op regel 16
- in de regels 17-20 wordt een eventuele uitzondering afgehandeld
2.3.1.10. De methode clearAllArticles
Opmerkingen:
- regel 1 – de methode ontvangt geen parameters en retourneert niets
- regel 6 – het verzoek om alle artikelen te verwijderen wordt uitgevoerd
- regels 7-10: er wordt omgegaan met een eventuele uitzondering
2.3.1.11. De methode changerStockArticle
Opmerkingen:
- regel 1 - de methode ontvangt als parameters het artikelnummer waarvan de voorraad moet worden gewijzigd, evenals de wijziging daarvan (positief of negatief). De methode retourneert het aantal regels dat door de bewerking c.a.d is gewijzigd. 0 of 1.
- regels 5-10: de query [updateStockCommand] wordt geïnitialiseerd. Ter herinnering: de tekst van de query SQL luidt als volgt:
updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
Merk op dat de voorraad alleen wordt gewijzigd als deze na de wijziging >=0 blijft.
- De query voor het bijwerken van de voorraad van het artikel wordt uitgevoerd in regel 13 en het resultaat wordt weergegeven
- regels 14-18: hier wordt een eventuele uitzondering afgehandeld
2.3.2. Genereren van de assembly van de laag [dao]
Het Visual Studio-project van deze nieuwe versie van de laag [dao] heeft de volgende structuur:

Het project is geconfigureerd om een DLL te genereren met de naam [webarticles-dao.dll]:
![]() | ![]() |
2.3.3. NUnit-tests van de laag [dao]
2.3.3.1. Aanmaken van een ODBC-Firebird-bron
Om onze nieuwe laag [dao] te testen, hebben we een gegevensbron ODBC nodig en dus een database. We gebruiken de Firebird-database SGBD (paragraaf 3.5). Met IBExpert (paragraaf 3.6) maken we de volgende artikeldatabase aan:
![]() | ![]() |
De beheerder van deze database is de gebruiker [SYSDBA] met het wachtwoord [masterkey]. We maken enkele artikelen aan:

We maken nu de volgende Firebird-bron ODBC aan (zie paragraaf 3.7):
![]() |
De aangemaakte bron ODBC heeft de volgende kenmerken:
- naam DSN: odbc-firebird-artikelen
- inlog-ID: SYSDBA
- bijbehorend wachtwoord: masterkey
2.3.3.2. De test klasse NUnit
We hebben al een testklasse geschreven voor de oorspronkelijk gebouwde laag [dao]. Zoals de lezer zich wellicht herinnert, testte deze klasse niet een specifieke klasse, maar de interface [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
' het te testen object
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' er wordt een instantie opgehaald van de Spring-objectgenerator
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' we vragen om het instantiëren van het object articlesdao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
....
We zien dat in de attribuutmethode <Setup()> aan Spring een verwijzing wordt gevraagd naar het singleton met de naam [articlesdao] van het type [IArticlesDao], dus van het type van de interface. Het singleton [articlesdao] werd gedefinieerd door het volgende configuratiebestand [spring-config.xml]:
<?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>
Laten we aantonen dat we met de oorspronkelijke testklasse onze nieuwe laag [dao] kunnen testen zonder aanpassingen of hercompilatie.
- Laten we in de Visual Studio-map van onze nieuwe laag [dao] de map [tests] (rechts hieronder) door de map [bin] uit het testproject van de oorspronkelijke laag [dao] (links hieronder) te kopiëren. Indien nodig kan de lezer het testproject van de eerste versie van de laag [dao] in het eerste deel van het artikel nog eens bekijken.
![]() | ![]() |
- in de map [tests] vervangen we de DLL en [webarticles-dao.dll] uit deoude laag [dao] te vervangen door de DLL en [webarticles-dao.dll] uit de nieuwe laag [dao]
- laten we het configuratiebestand [spring-config.xml] aanpassen om de nieuwe klasse [ArticlesDaoPlainODBC] te instantiëren:
Opmerkingen:
- regel 6: het object [articlesdao] is nu gekoppeld aan een instantie van de klasse [ ArticlesDaoPlainODBC]
- deze klasse heeft een constructor met drie argumenten:
- de naam van de bron DSN – regel 8
- de identiteit waarmee toegang tot de database wordt verkregen - regel 11
- het wachtwoord dat bij deze identiteit hoort – regel 14
We nemen hier de gegevens over van de ODBC-Firebird-bron die we eerder hebben aangemaakt.
2.3.3.3. Tests
We zijn nu klaar om te testen. Met behulp van de toepassing [Nunit-Gui] laden we de DLL en [test-webarticles-dao.dll] uit de bovenstaande map [tests] en voeren we de test [testGetAllArticles] uit:

Als we naar de bovenstaande schermafbeelding kijken, kunnen we de naam [NUnitTestArticlesDaoArrayList] betreuren die aanvankelijk aan de testklasse was gegeven. Dit leidt tot verwarring. Het is inderdaad de klasse [ArticlesDaoPlainODBC] die hier wordt getest. De schermafbeelding laat zien dat we de artikelen die we in de tabel [ARTICLES] hadden geplaatst, correct hebben opgehaald. Laten we nu alle tests uitvoeren:

In het linkervenster zie je de lijst met geteste methoden. De kleur van het puntje voor de naam van elke methode geeft aan of de methode geslaagd (groen) of mislukt (rood) is. De lezer die dit document op het scherm bekijkt, zal zien dat alle tests zijn geslaagd.
2.3.3.4. Conclusie
We hebben zojuist aangetoond dat:
- omdat de testklasse NUnit niet naar een klasse maar naar een interface verwees;
- omdat de exacte naam van de klasse waarmee de interface werd geïnstantieerd, in een configuratiebestand stond en niet in de code;
- omdat Spring zorgde voor het instantiëren van de klasse en het doorgeven van een verwijzing daarnaar aan de testcode;
bleef de testcode die voor de oorspronkelijke laag [dao] was geschreven, geldig voor een nieuwe implementatie van diezelfde laag. We hadden geen toegang nodig tot de code van de testklasse. We hebben alleen de gecompileerde versie ervan gebruikt, die was gegenereerd tijdens het testen van de oorspronkelijke laag [dao]. We zullen soortgelijke conclusies trekken wanneer de nieuwe laag [dao] in de applicatie [webarticles] moet worden geïntegreerd.
2.3.4. Integratie van de nieuwe laag [dao] in de applicatie [webarticles]
2.3.4.1. De integratietests
Ter herinnering: de oorspronkelijke versie van de applicatie [webarticles] was geïmplementeerd in de volgende map [runtime]:
![]() | ![]() |
![]() |
De lezer wordt verzocht eventueel paragraaf 2.2.4 nog eens door te nemen, waarin de details van de implementatie van de applicatie [webarticles] worden beschreven. We brengen de volgende wijzigingen aan in de inhoud van de map [runtime]:
- in het dossier [bin] wordt het bestand DLL van de oude laag [dao] vervangen door het bestand DLL van de nieuwe laag [dao]
- in [runtime] wordt het configuratiebestand [web.config] vervangen door een bestand dat rekening houdt met de nieuwe implementatieklasse van de laag [dao]:
![]() |
![]() |
Het nieuwe configuratiebestand [web.config] ziet er als volgt uit:
Opmerkingen:
- de regels 14-24 koppelen aan de singleton [articlesDao] een instantie van de nieuwe klasse [ArticlesDaoPlainODBC]. Dit is de enige wijziging. We zijn hier al op gestuit tijdens het testen van de nieuwe laag [dao].
We zijn klaar voor de tests. We configureren de webserver [Cassini] op dezelfde manier als in paragraaf 2.2.4. We initialiseren de artikeltabel [Firebird] met de volgende waarden:

Zorg ervoor dat zowel de Cassini-webserver als de SGBD en [Firebird] zijn gestart. Met een browser roepen we de URL [http://localhost/webarticles/main.aspx] op:

![]() |
Laten we nu de inhoud van de tabel [ARTICLES] in de database [Firebird] controleren:

De artikelen [parapluie] en [bottes] zijn aangekocht en de voorraad ervan is met de aangekochte hoeveelheid verminderd. Het artikel [chapeau] kon niet worden aangekocht omdat de gevraagde hoeveelheid groter was dan de voorraad. We nodigen de lezer uit om aanvullende tests uit te voeren.
2.3.4.2. Conclusie
Wat hebben we gedaan?
- we hebben de implementatieversie van de oude versie overgenomen;
- we hebben de DLL van de laag [dao] vervangen door een nieuwe versie. De DLL van de lagen [web] en [domain] zijn ongewijzigd gebleven;
- we hebben het configuratiebestand [web.config] aangepast zodat het rekening houdt met de nieuwe implementatieklasse van de laag [dao]
Dit alles is overzichtelijk en biedt de webapplicatie veel ruimte voor uitbreiding. Deze belangrijke kenmerken zijn te danken aan twee architecturale keuzes:
- toegang tot de lagen via interfaces
- de integratie en configuratie van de lagen door Spring.
We stellen nu een nieuwe implementatie van de laag [dao] voor.
2.4. De implementatieklasse [ArticlesDaoSqlServer]
De tweede implementatie van de laag [dao] gaat ervan uit dat de gegevens zich in een SQL Server-database bevinden. Microsoft stelt een SGBD ter beschikking, genaamd MSDE, wat een beperkte versie is van de SQL-server. In de bijlage, paragraaf 3.12, wordt uitgelegd hoe u deze kunt verkrijgen en installeren.
2.4.1. De code
De klasse [ArticlesDaoSqlServer] lijkt sterk op de eerder besproken klasse [ArticlesDaoPlainODBC]. Daarom zullen we alleen de wijzigingen ten opzichte van de vorige versie vermelden:
- de benodigde klassen bevinden zich in de naamruimte [System.Data.SqlClient] in plaats van in de naamruimte [System.Data.Odbc]
- de verbinding van het type [OdbcConnection] heeft nu het type [SqlConnection]
- de objecten van het type [OdbcCommand] hebben nu het type [SqlCommand]
- de syntaxis van de geconfigureerde SQL-query's verandert. De invoegquery wordt daarmee:
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
terwijl deze voorheen als volgt luidde:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
- De methode [ajouteArticle] wordt dan als volgt:
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' exclusief gedeelte
SyncLock Me
' de invoegquery wordt voorbereid
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
'deze wordt uitgevoerd
Return executeUpdate(insertCommand)
Catch ex As Exception
'fout in de query
Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
End Try
End SyncLock
End Function
- de fabrikant wordt ook gewijzigd:
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: naam van de te bereiken SQL-server
' databaseName: naam van de te bereiken database
' uid: gebruikers-ID
' wachtwoord: zijn wachtwoord
': de als argument doorgegeven databasenaam wordt opgehaald
Me.databaseName = databaseName
': de verbinding wordt tot stand gebracht
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' de query's worden voorbereid SQL
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
...
End Sub
De constructor accepteert nu vier parameters:
' server: naam van de instantie SQL die moet worden benaderd
' databaseName: naam van de te benaderen database
' uid: gebruikers-ID
' password: zijn wachtwoord
De volledige code van de klasse [ArticlesDaoSqlServer] is als volgt:
Imports System
Imports System.Collections
Imports System.Data.SqlClient
Namespace istia.st.articles.dao
Public Class ArticlesDaoSqlServer
Implements istia.st.articles.dao.IArticlesDao
' privévelden
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
' maker
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: naam van de te benaderen SQL-server
' databaseName: naam van de te bereiken database
' uid: gebruikers-ID
' wachtwoord: zijn wachtwoord
': de als argument doorgegeven databasenaam wordt opgehaald
Me.databaseName = databaseName
': de verbinding wordt tot stand gebracht
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' de query's worden voorbereid 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
' exclusief gedeelte
SyncLock Me
' de invoegquery wordt voorbereid
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
'wordt uitgevoerd
Return executeUpdate(insertCommand)
Catch ex As Exception
'queryfout
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
' exclusief gedeelte
SyncLock Me
' de query voor het bijwerken van de voorraad wordt voorbereid
With updateStockCommand.Parameters
.Clear()
.Add(New SqlParameter("@mvt", mouvement))
.Add(New SqlParameter("@id", idArticle))
End With
'wordt uitgevoerd
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'fout bij verzoek
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
' exclusief gedeelte
SyncLock Me
Try
'de invoeropdracht wordt uitgevoerd
executeUpdate(deleteAllCommand)
Catch ex As Exception
'queryfout
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
' exclusief gedeelte
SyncLock Me
Try
'de select-query wordt uitgevoerd
Dim articles As IList = executeQuery(selectAllCommand)
'de lijst wordt geretourneerd
Return articles
Catch ex As Exception
'queryfout
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
' exclusieve sectie
SyncLock Me
' de SELECT-query wordt voorbereid
With selectSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'wordt uitgevoerd
Try
'de query wordt uitgevoerd
Dim articles As IList = executeQuery(selectSomeCommand)
'wordt gecontroleerd of het artikel is gevonden
If articles.Count = 0 Then Return Nothing
'het artikel wordt teruggegeven
Return CType(articles.Item(0), Article)
Catch ex As Exception
'fout bij de query
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
' exclusief gedeelte
SyncLock Me
' de update-query wordt voorbereid
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
' de query wordt uitgevoerd
Try
'de invoegopdracht wordt uitgevoerd
Return executeUpdate(updatecommand)
Catch ex As Exception
'queryfout
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
' exclusieve sectie
SyncLock Me
' de verwijderopdracht wordt voorbereid
With deleteSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'wordt uitgevoerd
Try
'de verwijderingsquery wordt uitgevoerd
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'fout bij query
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
' uitvoering van een query SELECT
' definitie van het object dat toegang geeft tot alle rijen van de resultatentabel
Dim myReader As SqlDataReader = Nothing
Try
'er wordt een verbinding tot stand gebracht met de BDD
connexion.Open()
'de query wordt uitgevoerd
myReader = query.ExecuteReader()
'er wordt een lijst met artikelen gedeclareerd om deze later terug te sturen
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
': er wordt een artikel voorbereid met de waarden van de reader
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)
': het artikel wordt aan de lijst toegevoegd
articles.Add(unArticle)
End While
'het resultaat wordt teruggestuurd
Return articles
Finally
' vrijgeven van de bronnen
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
' uitvoering van een updateverzoek
Try
'er wordt een verbinding tot stand gebracht met de BDD
connexion.Open()
'de aanvraag wordt uitgevoerd
Return updateCommand.ExecuteNonQuery()
Finally
' vrijgeven van bronnen
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
De lezer wordt verzocht deze code te lezen in het licht van de eerder gemaakte opmerkingen over de klasse [ArticlesDaoPlainODBC].
2.4.2. Genereren van de assembly van de laag [dao]
Het nieuwe Visual Studio-project heeft de volgende structuur:

Het project is geconfigureerd om een DLL te genereren met de naam [webarticles-dao.dll]:
![]() | ![]() |
2.4.3. NUnit-tests van de laag [dao]
2.4.3.1. Aanmaken van een SQL Server- -gegevensbron
Om onze nieuwe laag [dao] te testen, hebben we een gegevensbron SQL Server nodig en dus ook de SGBD SQL Server. We zullen in feite de SGBD MSDE (MicroSoft Data Engine) (paragraaf 3.12), een versie van de SQL-server die alleen beperkt is door het aantal gelijktijdige gebruikers dat wordt toegelaten. Met [EMS MS SQL Manager] (paragraaf 3.14) maken we de volgende artikeldatabase aan in een MSDE-instantie met de naam [portable1_tahe\msde140405]:
![]() | ![]() |

De database is eigendom van de gebruiker [mdparticles] met wachtwoord [admarticles]. De Transact-SQL-opdracht voor het aanmaken van de tabel [ARTICLES] is als volgt:
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
We maken een aantal artikelen aan:

2.4.3.2. De testklasse NUnit
De NUnit-testklasse van de implementatieklasse [ArticlesDaoSqlServer] is dezelfde als die van de klasse [ArticlesDaoPlainODBC] (zie paragraaf 2.3.3.2). We volgen een vergelijkbare werkwijze om de NUnit-test voor de klasse voor te bereiden:
- we maken in de Visual Studio-map van het project [dao-sqlserver] de map [tests] (rechts) aan door de map [tests] uit het project [dao-odbc] (links) te kopiëren:
![]() | ![]() |
- in de map [tests] van het project [dao-sqlserver], vervangen we de DLL [webarticles-dao.dll] door de DLL [webarticles-dao.dll] die voortkomt uit de generatie van het project [dao-sqlserver]
- we passen het configuratiebestand [spring-config.xml] aan om de nieuwe klasse [ArticlesDaoSqlServer] te instantiëren:
Opmerkingen:
- regel 7: het object [articlesdao] is nu gekoppeld aan een instantie van de klasse [ ArticlesDaoSqlServeur]
- deze klasse heeft een constructor met vier argumenten:
- de naam van de gebruikte instantie MSDE – regel 9
- de naam van de database – regel 12
- de identiteit waarmee toegang tot de database wordt verkregen – regel 15
- het wachtwoord dat bij deze identiteit hoort – regel 18
We nemen hier de gegevens over uit de bron MSDE die we eerder hebben aangemaakt.
2.4.3.3. Tests
We zijn klaar om te testen. Met behulp van de applicatie [Nunit-Gui] laden we het bestand DLL [test-webarticles-dao.dll] uit de bovenstaande map [tests] en voeren we de test [testGetAllArticles] uit:

Ondanks de naam [NUnitTestArticlesDaoArrayList] die aanvankelijk aan de testklasse was gegeven en die behouden is gebleven omdat we de DLL en [tests-webarticles-dao.dll] uit deze klasse gebruiken, wordt hier wel degelijk de klasse [ArticlesDaoSqlserver] getest. De schermafbeelding laat zien dat we de artikelen die we in de tabel [ARTICLES] hadden geplaatst, correct hebben opgehaald. Laten we nu alle tests uitvoeren:

In het linkervenster zien we de lijst met geteste methoden. De kleur van het puntje voor de naam van elke methode geeft aan of de methode geslaagd (groen) of mislukt (rood) is. De lezer die dit document op het scherm bekijkt, zal zien dat alle tests zijn geslaagd.
2.4.4. Integratie van de nieuwe laag [dao] in de toepassing [webarticles]
We volgen de procedure die in paragraaf 2.3.4 wordt uitgelegd. We brengen de volgende wijzigingen aan in de inhoud van de map [runtime]:
- in de map [bin] wordt de DLL van deoude laag [dao] wordt vervangen door de DLL van de nieuwe laag [dao], geïmplementeerd door de klasse [ArticlesDaoSqlServer]
- in [runtime] wordt het configuratiebestand [web.config] vervangen door een bestand dat rekening houdt met de nieuwe implementatieklasse:
Opmerkingen:
- de regels 15-33 koppelen aan het singleton [articlesDao] een instantie van de nieuwe klasse [ArticlesDaoSqlServer]. Dit is de enige wijziging. We zijn hier al op gestuit tijdens het testen van de nieuwe laag [dao]
We zijn klaar voor de tests. We behouden dezelfde configuratie van de webserver [Cassini] als voorheen. We initialiseren de artikeltabel [MSDE] met de volgende waarden:

Zorg ervoor dat zowel de Cassini-webserver als de SGBD MSDE (hier de instantie portable1_tahe\msde140405) zijn gestart. Met een browser roepen we de URL en [http://localhost/webarticles/main.aspx] op:

![]() |
Laten we nu de inhoud van de tabel [ARTICLES] in de database [MSDE] controleren:

De artikelen [ballon foot] en [raquette tennis] zijn aangekocht en de voorraad ervan is met de aangekochte hoeveelheid verminderd. Het artikel [rollers] kon niet worden aangekocht omdat de gevraagde hoeveelheid groter was dan de voorraad. We nodigen de lezer uit om aanvullende tests uit te voeren.
2.4.5. De implementatieklasse [ArticlesDaoOleDb]
2.4.5.1. De gegevensbron- s OleDb
De derde implementatie van de laag [dao] gaat ervan uit dat de gegevens zich in een database bevinden die toegankelijk is via een driver OleDb. Het principe van de bronnen OleDb is analoog aan dat van de bronnen ODBC. Een programma dat gebruikmaakt van een bron OleDb doet dit via een standaardinterface die alle bronnen OleDb gemeen hebben. Het wisselen van OleDb-bron komt neer op het wisselen van OleDb-stuurprogramma. De code zelf wordt niet gewijzigd.
Met Visual Studio kunt u zien welke stuurprogramma’s OleDb op uw computer beschikbaar zijn:
- open de serververkenner via [Affichage/Explorateur de serveurs]:

- Om een nieuwe verbinding toe te voegen, klikt u met de rechtermuisknop op [Connexion de données] en kiest u de optie [Ajouter une connexion]. Er verschijnt dan een wizard waarmee u de eigenschappen van de verbinding kunt instellen:

- Het venster [Fournisseur] toont de lijst met beschikbare stuurprogramma’s OLEDB. Voor de nieuwe laag [dao] gaan we een driver [Microsoft Jet 4.0 OLE DB Provider] gebruiken die toegang geeft tot de databases ACCESS.
- Laten we Visual Studio even verlaten om de basis ACCESS [articles.mdb] aan te maken, met de volgende enige tabel:

- De structuur van de tabel is als volgt:
getal - geheel getal - primaire sleutel | |
tekst - 20 tekens - | |
getal - dubbele drijvende komma | |
getal - geheel getal | |
getal - geheel getal |
- Laten we teruggaan naar Visual Studio en een nieuwe verbinding aanmaken zoals eerder uitgelegd:

- We kiezen de driver [Microsoft Jet 4.0] en gaan naar het paneel [Connexion]:

- Met de knop [1] selecteren we de zojuist aangemaakte database ACCESS en voltooien we de definitie van de verbinding met de knop [Terminer]. De aangemaakte verbinding verschijnt nu in de lijst met beschikbare verbindingen:

- Door te dubbelklikken op de tabel [ARTICLES] krijgen we toegang tot de inhoud ervan:

- Vervolgens kunt u rijen in de tabel toevoegen, wijzigen of verwijderen.
- Selecteer in de serververkenner de nieuwe verbinding om het eigenschappenvenster te openen:

- Het is handig om de verbindingsstring te kennen. Deze hebben we nodig om verbinding te maken met de database:
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
- Van deze tekenreeks onthouden we alleen de volgende elementen:
2.4.5.2. De code van de klasse [ArticlesDaoOleDb]
De klasse [ArticlesDaoOleDb] lijkt sterk op de eerder besproken klasse [ArticlesDaoPlainODBC]. Daarom vermelden we alleen de wijzigingen ten opzichte van de vorige versie:
- de benodigde klassen bevinden zich in de naamruimte [System.Data.OleDb] in plaats van in de naamruimte [System.Data.Odbc]
- de verbinding van het type [OdbcConnection] heeft nu het type [OleDbConnection]
- de objecten van het type [OdbcCommand] hebben nu het type [OleDbCommand]
De constructor van de klasse accepteert als enige parameter de verbindingsstring naar de database:
' constructor
Public Sub New(ByVal connectString As String)
' connectString: verbindingsstring naar de bron OleDb
'de verbinding wordt geïnitialiseerd
connexion = New OleDbConnection(connectString)
' de verzoeken worden voorbereid SQL
...
End Sub
De volledige code van de klasse [ArticlesDaoOleDb] is als volgt:
Imports System
Imports System.Collections
Imports System.Data.OleDb
Namespace istia.st.articles.dao
Public Class ArticlesDaoOleDb
Implements istia.st.articles.dao.IArticlesDao
' privévelden
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
' constructor
Public Sub New(ByVal connectString As String)
' connectString: verbindingsstring naar de bron OleDb
'de verbinding wordt geïnitialiseerd
connexion = New OleDbConnection(connectString)
' de verzoeken worden voorbereid 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
' exclusief gedeelte
SyncLock Me
' de invoegverzoek wordt voorbereid
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
'wordt uitgevoerd
Return executeUpdate(insertCommand)
Catch ex As Exception
'fout in de query
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
' exclusief gedeelte
SyncLock Me
' de query voor het bijwerken van de voorraad wordt voorbereid
With updateStockCommand.Parameters
.Clear()
.Add(New OleDbParameter("mvt1", mouvement))
.Add(New OleDbParameter("id", idArticle))
.Add(New OleDbParameter("mvt2", mouvement))
End With
'wordt uitgevoerd
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'fout bij verzoek
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
' exclusief gedeelte
SyncLock Me
Try
'de invoeropdracht wordt uitgevoerd
executeUpdate(deleteAllCommand)
Catch ex As Exception
'fout bij de query
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
' exclusief gedeelte
SyncLock Me
Try
'de select-query wordt uitgevoerd
Dim articles As IList = executeQuery(selectAllCommand)
'de lijst wordt geretourneerd
Return articles
Catch ex As Exception
'queryfout
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
' exclusieve sectie
SyncLock Me
' de SELECT-query wordt voorbereid
With selectSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'deze wordt uitgevoerd
Try
': de query wordt uitgevoerd
Dim articles As IList = executeQuery(selectSomeCommand)
'wordt getest of het artikel is gevonden
If articles.Count = 0 Then Return Nothing
'het artikel wordt teruggestuurd
Return CType(articles.Item(0), Article)
Catch ex As Exception
'fout bij de zoekopdracht
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
' exclusief gedeelte
SyncLock Me
' de update-query wordt voorbereid
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
' de query wordt uitgevoerd
Try
'de invoegopdracht wordt uitgevoerd
Return executeUpdate(updatecommand)
Catch ex As Exception
'fout in de query
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
' exclusieve sectie
SyncLock Me
' de verwijderopdracht wordt voorbereid
With deleteSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'wordt uitgevoerd
Try
'de verwijderingsquery wordt uitgevoerd
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'fout bij query
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
' uitvoering van een query SELECT
' definitie van het object dat toegang geeft tot alle rijen van de resultatentabel
Dim myReader As OleDbDataReader = Nothing
Try
'er wordt een verbinding tot stand gebracht met de BDD
connexion.Open()
'de query wordt uitgevoerd
myReader = query.ExecuteReader()
'er wordt een lijst met artikelen gedeclareerd om deze later terug te sturen
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
': er wordt een artikel voorbereid met de waarden van de reader
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)
': het artikel wordt aan de lijst toegevoegd
articles.Add(unArticle)
End While
'het resultaat wordt teruggestuurd
Return articles
Finally
' vrijgeven van de bronnen
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
' uitvoering van een updateverzoek
Try
'er wordt een verbinding tot stand gebracht met de BDD
connexion.Open()
'de query wordt uitgevoerd
Return sqlCommand.ExecuteNonQuery()
Finally
' vrijgeven van bronnen
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
De lezer wordt verzocht deze code te lezen in het licht van de eerder gemaakte opmerkingen over de klasse [ArticlesDaoPlainODBC].
2.4.5.3. Genereren van de assembly van de laag [dao]
Het nieuwe Visual Studio-project heeft de volgende structuur:

Het project is geconfigureerd om een DLL te genereren met de naam [webarticles-dao.dll]:
![]() | ![]() |
2.4.5.4. NUnit-tests voor de laag [dao]
2.4.5.4.1. De testklasse NUnit
De NUnit-testklasse voor de implementatieklasse [ArticlesDaoOleDb] is dezelfde als die voor de klasse [ArticlesDaoPlainODBC] (zie paragraaf 2.3.3.2). We volgen een vergelijkbare aanpak om de NUnit-test voor de klasse voor te bereiden:
- we maken in de Visual Studio-map van het project [dao-oledb] de map [tests] (rechts) aan door de map [tests] uit het project [dao-odbc] (links) te kopiëren:
![]() | ![]() |
- in de map [tests] van het project [dao-oledb] vervangen we de DLL [webarticles-dao.dll] door de DLL [webarticles-dao.dll], afkomstig uit de generatie van het project [dao-oledb]
- we wijzigen het configuratiebestand [spring-config.xml] om de nieuwe klasse [ArticlesDaoOleDb] te instantiëren:
Opmerkingen:
- regel 7: het object [articlesdao] is nu gekoppeld aan een instantie van de klasse [ ArticlesDaoOleDb]
- deze klasse heeft een constructor met één argument: de verbindingsstring naar de database OleDb ACCESS - regel 9
2.4.5.4.2. Tests
We zijn klaar om te testen. Met behulp van de applicatie [Nunit-Gui] laden we de DLL [test-webarticles-dao.dll] uit de bovenstaande map [tests] en voeren we de test [testGetAllArticles] uit:

Ondanks de naam [NUnitTestArticlesDaoArrayList] die aanvankelijk aan de testklasse was gegeven, wordt hier inderdaad de klasse [ArticlesDaoOleDb] getest. De schermafbeelding laat zien dat we de artikelen die we in de tabel [ARTICLES] hadden geplaatst, correct hebben opgehaald. Laten we nu alle tests uitvoeren:

De lezer die dit document op het scherm bekijkt, zal zien dat alle tests zijn geslaagd (groene kleur).
2.4.5.5. Integratie van de nieuwe laag [dao] in de toepassing [webarticles]
We volgen de procedure die in paragraaf 2.3.4 wordt uitgelegd. We brengen de volgende wijzigingen aan in de inhoud van de map [runtime]:
- in de map [bin] wordt het bestand DLL uit deoude laag [dao] wordt vervangen door de DLL van de nieuwe laag [dao], geïmplementeerd door de klasse [ArticlesDaoOleDb]
- in [runtime] wordt het configuratiebestand [web.config] vervangen door een bestand dat rekening houdt met de nieuwe implementatieklasse:
Opmerkingen:
- de regels 14-18 koppelen aan de singleton [articlesDao] een instantie van de nieuwe klasse [ArticlesDaoOleDb]. Dit is de enige wijziging.
We behouden dezelfde configuratie van de webserver [Cassini] als voorheen. We initialiseren de artikeltabel met de volgende waarden:

Zorg ervoor dat de artikeldatabase niet wordt gebruikt door een programma zoals Visual Studio of ACCESS. Met een browser roepen we de URL [http://localhost/webarticles/main.aspx] op:

![]() |
Laten we nu de inhoud van de tabel [ARTICLES] vergelijken met die van ACCESS:

De artikelen [pantalon] en [jupe] zijn aangekocht en de voorraad is met de aangekochte hoeveelheid verminderd. Het artikel [manteau] kon niet worden aangekocht omdat de gevraagde hoeveelheid groter was dan de voorraad. We nodigen de lezer uit om aanvullende tests uit te voeren.
2.5. De implementatieklasse [ArticlesDaoFirebirdProvider]
2.5.1. De Firebird-net-provider
We hebben al gebruikgemaakt van een gegevensbron [Firebird], die we via een driver ODBC hebben gebruikt. Hoewel de stuurprogramma’s ODBC de code waarin ze worden gebruikt een hoge mate van herbruikbaarheid bieden, presteren ze minder goed dan stuurprogramma’s die specifiek zijn geschreven voor de beoogde SGBD. De SGBD [Firebird] kan worden gebruikt via een bibliotheek met specifieke klassen die kan worden gedownload van de Firebird-website [http://firebird.sourceforge.net/]. De downloadpagina biedt de volgende links (april 2005):
![]()
De link [firebird-net-provider] is de link die je moet gebruiken om de .NET-klassen voor toegang tot SGBD Firebird te downloaden. Na installatie van het pakket ontstaat er een map die er ongeveer zo uitziet:

Twee elementen zijn voor ons van belang:
- [FirebirdSql.Data.Firebird.dll]: de assembly die de .NET-klassen bevat voor toegang tot SGBD Firebird
- [FirebirdNETProviderSDK.chm]: de documentatie over deze klassen
Om ervoor te zorgen dat een Visual Studio-project deze klassen kan gebruiken, doen we vervolgens twee dingen:
- we plaatsen de assembly [FirebirdSql.Data.Firebird.dll] in de map [bin] van het project
- we voegen diezelfde assembly toe aan de verwijzingen van het project
2.5.2. De code van de klasse [ArticlesDaoFirebirdProvider]
De klasse [ArticlesDaoFirebirdProvider] lijkt sterk op de eerder besproken klasse [ArticlesDaoSqlServer]. We zullen daarom alleen de wijzigingen ten opzichte van die versie vermelden:
- de benodigde klassen bevinden zich in de naamruimte [FirebirdSql.Data.Firebird] in plaats van in de naamruimte [System.Data.SqlClient]
- de verbinding van het type [SqlConnection] heeft nu het type [FbConnection]
- de objecten van het type [SqlCommand] hebben nu het type [FbCommand]
- de objecten van het type [SqlParameter] hebben nu het type [FbParameter]
De constructor van de klasse accepteert vier parameters, waarmee hij de verbindingsstring naar de database opbouwt:
' constructor
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: naam van de hostmachine van SGBD
' databaseName: pad naar de database
' uid: gebruikers-ID van de gebruiker die inlogt
' password: zijn wachtwoord
...
End Sub
De volledige code van de klasse [ArticlesDaoFirebirdProvider] is als volgt:
Imports System
Imports System.Collections
Imports FirebirdSql.Data.Firebird
Namespace istia.st.articles.dao
Public Class ArticlesDaoFirebirdProvider
Implements istia.st.articles.dao.IArticlesDao
' privévelden
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
' fabrikant
Public Sub New(ByVal serveur As String, ByVal databasePath As String, ByVal uid As String, ByVal password As String)
' server: naam van de hostmachine van de SGBD Firebird
' databaseName: pad naar de te gebruiken database
' uid: gebruikers-ID van de gebruiker die verbinding maakt met de database
' password: zijn wachtwoord
': de als argument doorgegeven naam van de database wordt opgehaald
Me.databasePath = databasePath
': de verbinding wordt tot stand gebracht
Dim connectString As String = String.Format("DataSource={0};Database={1};User={2};Password={3}", serveur, databasePath, uid, password)
connexion = New FbConnection(connectString)
' de query's worden voorbereid 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
' exclusief gedeelte
SyncLock Me
' de invoegquery wordt voorbereid
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
'wordt uitgevoerd
Return executeUpdate(insertCommand)
Catch ex As Exception
'fout in de query
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
' exclusief gedeelte
SyncLock Me
' de query voor het bijwerken van de voorraad wordt voorbereid
With updateStockCommand.Parameters
.Clear()
.Add(New FbParameter("@mvt", mouvement))
.Add(New FbParameter("@id", idArticle))
End With
'wordt uitgevoerd
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'fout bij verzoek
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
' exclusief gedeelte
SyncLock Me
Try
'de invoeropdracht wordt uitgevoerd
executeUpdate(deleteAllCommand)
Catch ex As Exception
'fout bij de query
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
' exclusief gedeelte
SyncLock Me
Try
'de select-query wordt uitgevoerd
Dim articles As IList = executeQuery(selectAllCommand)
'de lijst wordt geretourneerd
Return articles
Catch ex As Exception
'queryfout
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
' exclusieve sectie
SyncLock Me
' de SELECT-query wordt voorbereid
With selectSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'wordt uitgevoerd
Try
'de query wordt uitgevoerd
Dim articles As IList = executeQuery(selectSomeCommand)
'wordt gecontroleerd of het artikel is gevonden
If articles.Count = 0 Then Return Nothing
'het artikel wordt teruggestuurd
Return CType(articles.Item(0), Article)
Catch ex As Exception
'fout bij de query
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
' exclusief gedeelte
SyncLock Me
' de update-query wordt voorbereid
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
' de query wordt uitgevoerd
Try
'de invoegopdracht wordt uitgevoerd
Return executeUpdate(updatecommand)
Catch ex As Exception
'queryfout
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
' exclusieve sectie
SyncLock Me
' de verwijderopdracht wordt voorbereid
With deleteSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'wordt uitgevoerd
Try
'de verwijderingsquery wordt uitgevoerd
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'fout bij query
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
' uitvoering van een query SELECT
' definitie van het object dat toegang geeft tot alle rijen van de resultatentabel
Dim myReader As FbDataReader = Nothing
Try
'er wordt een verbinding tot stand gebracht met de BDD
connexion.Open()
'de query wordt uitgevoerd
myReader = query.ExecuteReader()
'er wordt een lijst met artikelen gedeclareerd om deze later terug te sturen
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
': er wordt een artikel voorbereid met de waarden van de reader
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)
': het artikel wordt aan de lijst toegevoegd
articles.Add(unArticle)
End While
'het resultaat wordt teruggestuurd
Return articles
Finally
' vrijgeven van de bronnen
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
' uitvoering van een updateverzoek
Try
': er wordt een verbinding tot stand gebracht met BDD
connexion.Open()
': de query wordt uitgevoerd
Return updateCommand.ExecuteNonQuery()
Finally
' vrijgeven van bronnen
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
De lezer wordt verzocht deze code te lezen in het licht van de eerder gemaakte opmerkingen over de klasse [ArticlesDaoSqlServer].
2.5.3. Genereren van de assembly van de laag [dao]
Het nieuwe Visual Studio-project heeft de volgende structuur:

Let op de aanwezigheid van de assembly [FirebirdSql.Data.Firebird.dll] in de verwijzingen van het project. Deze DLL is in de map [bin] van het project geplaatst. Het project is geconfigureerd om een DLL te genereren met de naam [webarticles-dao.dll]:
![]() | ![]() |
2.5.4. NUnit-tests van de laag [dao]
2.5.4.1. De testklasse NUnit
De Nunit-testklasse van de implementatieklasse [ArticlesDaoFirebirdProvider] is dezelfde als die van de klasse [ArticlesDaoPlainODBC] (zie paragraaf 2.3.3.2). We volgen een vergelijkbare werkwijze om de Nunit-test voor de klasse [ArticlesDaoFirebirdProvider] voor te bereiden:
- we maken in de Visual Studio-map van het project [dao-firebird-provider] de map [tests] (rechts) aan door de map [bin] uit het testproject van de laag [dao-odbc] (links) te kopiëren:
![]() | ![]() |
- in de map [tests] vervangen we de bestanden DLL en [webarticles-dao.dll] door de bestanden DLL en [webarticles-dao.dll], afkomstig uit de generatie van het project [dao-firebird-provider]
- we passen het configuratiebestand [spring-config.xml] aan om de nieuwe klasse [ArticlesDaoFirebirdProvider] te instantiëren:
Opmerkingen:
- regel 7: het object [articlesdao] is nu gekoppeld aan een instantie van de klasse [ArticlesDaoFirebirdProvider]
- deze klasse heeft een constructor met vier argumenten
- de hostmachine van SGBD – regel 9
- het pad naar de Firebird-database – regel 12
- de gebruikersnaam van de gebruiker die inlogt – regel 15
- zijn wachtwoord – regel 18
2.5.4.2. Tests
De tabel [ARTICLES] van de gegevensbron is gevuld met de volgende artikelen (gebruik IBExpert):

We zijn klaar om te testen. Met behulp van de applicatie [Nunit-Gui] laden we de DLL en [test-webarticles-dao.dll] uit de bovenstaande map [tests] en voeren we de test [testGetAllArticles] uit:

Ondanks de naam [NUnitTestArticlesDaoArrayList] die aanvankelijk aan de testklasse was gegeven, wordt hier inderdaad de klasse [ArticlesDaoFirebirdProvider] getest. De schermafbeelding laat zien dat we de artikelen die we in de tabel [ARTICLES] hadden geplaatst, correct hebben opgehaald. Laten we nu alle tests uitvoeren:

De lezer die dit document op het scherm bekijkt, zal zien dat alle tests zijn geslaagd (groene kleur). Wat hij niet kan zien, is dat de tests aanzienlijk sneller zijn verlopen dan bij de artikeldatabase die via een driver ODBC uit onze eerste implementatie werd benaderd.
2.5.5. Integratie van de nieuwe laag [dao] in de applicatie [webarticles]
We volgen de werkwijze die al tweemaal is uitgelegd, met name in paragraaf 2.3.4. We brengen de volgende wijzigingen aan in de inhoud van de map [runtime]:
- in de map [bin] wordt de DLL van deoude laag [dao] wordt vervangen door de DLL van de nieuwe laag [dao], geïmplementeerd door de klasse [ArticlesDaoFirebirdProvider]. We plaatsen daar ook de DLL die nodig is voor Firebird [FirebirdSql.Data.Firebird.dll]:

- in [runtime] wordt het configuratiebestand [web.config] vervangen door een bestand dat rekening houdt met de nieuwe implementatieklasse:
Opmerkingen:
- de regels 14-27 koppelen aan de singleton [articlesDao] een instantie van de nieuwe klasse [ArticlesDaoFirebirdProvider]. Dit is de enige wijziging.
We zijn klaar om te testen. We configureren de webserver [Cassini] zoals in de vorige tests. We initialiseren de artikeltabel met de volgende waarden:

Met een browser roepen we de pagina URL [http://localhost/webarticles/main.aspx] op:

![]() |
Laten we nu de inhoud van de tabel [ARTICLES] controleren:

De artikelen [crayon bille] en [ramette 50 feuilles] zijn aangekocht en de voorraad is met de aangekochte hoeveelheid verminderd. Het artikel [stylo plume] kon niet worden aangekocht omdat de gevraagde hoeveelheid groter was dan de voorraad. We nodigen de lezer uit om aanvullende tests uit te voeren.
2.5.6. De implementatieklasse [ArticlesDaoSqlMap]
2.5.6.1. Het Ibatis-product SqlMap
We hebben vier verschillende implementaties geschreven van de laag [dao] van onze applicatie [webarticles]. Telkens konden we de nieuwe laag [dao] integreren in de applicatie [webarticles] zonder de twee andere lagen [web] en [domain] opnieuw te hoeven compileren. Dit werd, zoals gezegd, bereikt door twee architecturale keuzes:
- toegang tot de lagen via interfaces
- de integratie van de lagen door Spring
We willen nog een stapje verder gaan. Hoewel ze verschillend zijn, vertonen onze vier implementaties van de laag [dao] opvallende overeenkomsten. Nadat de eerste implementatie was geschreven, werden de drie andere vrijwel verkregen door middel van kopiëren en plakken en het vervangen van bepaalde sleutelwoorden door andere sleutelwoorden. De logica zelf is niet gewijzigd. Men kan zich afvragen of het niet mogelijk zou zijn om één implementatie te hebben die ons zou bevrijden van de verschillende manieren om toegang te krijgen tot de gegevens. We hebben er vier gebruikt:
- toegang via een stuurprogramma ODBC tot een gegevensbron ODBC
- directe toegang tot een SQL Server-database
- toegang via een Ole Db-driver tot een Ole Db-gegevensbron
- directe toegang tot een Firebird-database
De Ibatis-tool SqlMap [[http://www.ibatis.com/] maakt het mogelijk om gegevenslaagtoegang te ontwikkelen die onafhankelijk is van de werkelijke aard van de gegevensbron. De toegang tot de gegevens wordt gewaarborgd door middel van:
- configuratiebestanden waarin de informatie is opgenomen die de gegevensbron definieert en de bewerkingen die men erop wil uitvoeren
- een klassenbibliotheek die op deze informatie is gebaseerd om toegang te krijgen tot de gegevens
De tool Ibatis SqlMap is oorspronkelijk ontwikkeld voor het Java-platform. De port naar het .NET-platform is recent en lijkt gedeeltelijk bugs te bevatten (persoonlijke mening die nader onderzoek vereist). Aangezien de tool zich echter op het Java-platform heeft bewezen, lijkt het interessant om de .NET-versie te presenteren.
2.5.6.2. Waar vind je IBATIS SqlMap?
De hoofdwebsite van Firebird is [http://www.ibatis.com/]. Op de downloadpagina staan de volgende links:

We kiezen de link [Stable Binaries], die ons naar [SourceForge.net] brengt. Volg het downloadproces tot het einde. We krijgen een zip-bestand met de volgende bestanden:

In een Visual Studio-project waarin Ibatis SqlMap wordt gebruikt, moet je twee dingen doen:
- de bovenstaande bestanden in de map [bin] van het project plaatsen
- aan het project een verwijzing naar elk van deze bestanden toevoegen
2.5.6.3. De configuratiebestanden van Ibatis SqlMap
Een gegevensbron [SqlMap] wordt gedefinieerd aan de hand van de volgende configuratiebestanden:
- providers.config: definieert de klassenbibliotheken die moeten worden gebruikt om toegang te krijgen tot de gegevens
- sqlmap.config: definieert de kenmerken van de te tot stand te brengen verbinding
- toewijzingsbestanden: definiëren de bewerkingen die op de gegevens moeten worden uitgevoerd
De logica achter deze bestanden is als volgt:
- om toegang te krijgen tot de gegevens, hebben we een verbinding nodig. Om deze weer te geven, zijn we al verschillende klassen tegengekomen: OdbcConnection, SqlConnection, OleDbConnection, FbConnection. We hebben ook een object [Command] nodig om SQL-verzoeken te verzenden: OdbcCommand, SqlCommand, OleDbCommand, FbCommand. Enz. In het bestand [providers.config] definiëren we alle klassen die we nodig hebben.
- Het bestand [sqlmap.config] definieert in wezen de verbindingsstring naar de database die de gegevens bevat. De verbinding met de database wordt geopend door de klasse [Connection] te instantiëren, die is gedefinieerd in [providers.config]; aan de constructor daarvan wordt de verbindingsstring doorgegeven die is gedefinieerd in [sqlmap.config].
- De mappingbestanden definiëren:
- koppelingen tussen rijen in gegevenstabellen en de klasse .NET, waarvan de instanties deze rijen zullen bevatten
- de uit te voeren bewerkingen SQL. Deze worden aangeduid met een naam. De code .NET voert deze bewerkingen uit op basis van hun naam, waardoor alle SQL-code uit de code .NET wordt verwijderd.
2.5.6.4. De configuratiebestanden van het project [dao-sqlmap]
Laten we aan de hand van een voorbeeld eens kijken naar de precieze aard van de configuratiebestanden van SqlMap. We gaan uit van het geval waarin de gegevensbron de Firebird-bron ODBC is uit paragraaf 2.3.3.1.
2.5.6.4.1. providers.config
Het bestand [providers.config] voor een bron ODBC ziet er als volgt uit:
Opmerkingen:
- een bestand [providers.config] wordt meegeleverd met het pakket [SqlMap]. Het biedt verschillende standaardproviders aan. De bovenstaande code is rechtstreeks afkomstig uit dit bestand.
- Een <provider> heeft een naam – regel 6 – die willekeurig kan zijn
- Een <provider> kan al dan niet geactiveerd zijn ([enabled=true] of [enabled=false]). Als deze geactiveerd is, moet de in regel 8 genoemde DLL toegankelijk zijn. Een bestand [providers.config] kan meerdere <provider>-tags bevatten.
- regel 8 - naam van de assembly die de gedefinieerde klassen bevat regels 9-15
- regel 9 - klasse die moet worden gebruikt om een verbinding tot stand te brengen
- regel 10 - klasse die moet worden gebruikt om een [Command]-object te maken voor het verzenden van SQL-opdrachten
- regel 11 - klasse die moet worden gebruikt voor het beheren van de parameters van een geconfigureerd commando SQL
- regel 12 - klasse voor het opsommen van de mogelijke gegevenstypen voor de velden van een tabel
- regel 13 - naam van de eigenschap van een [Parameter]-object die het type van de waarde van deze parameter bevat
- regel 14 - naam van de klasse [Adapter] waarmee [DataSet]-objecten kunnen worden aangemaakt op basis van de gegevensbron
- regel 15 - naam van de klasse [CommandBuilder] die, in combinatie met een object [Adapter], het mogelijk maakt om automatisch de eigenschappen [InsertCommand, DeleteCommand, UpdateCommand] van dat object te genereren op basis van de eigenschap [SelectCommand]
- regels 16 - 19 - hier wordt gedefinieerd hoe de geconfigureerde SQL-opdrachten worden beheerd. Afhankelijk van het geval moet bijvoorbeeld het volgende worden geschreven:
of
In het eerste geval is er sprake van formele positieparameters. De daadwerkelijke waarden hiervan moeten in de volgorde van de formele parameters worden opgegeven. In het tweede geval gaat het om benoemde parameters. Aan een dergelijke parameter wordt een waarde toegekend door de naam ervan te specificeren. De volgorde doet er niet meer toe.
- regel 16 – hier wordt aangegeven dat de bronnen ODBC positieparameters gebruiken
- regels 17-19 – hebben betrekking op benoemde parameters. Hier zijn er geen.
Aan de hand van deze informatie weet SqlMap bijvoorbeeld welke klasse het moet instantiëren om een verbinding tot stand te brengen. In dit geval is dat de klasse [OdbcConnection] (regel 9).
2.5.6.4.2. sqlmap.config
Het bestand [providers.config] definieert de klassen die moeten worden gebruikt om toegang te krijgen tot een bron ODBC. Het vermeldt geen bron ODBC. Dat doet het bestand [sqlmap.config]:
Opmerkingen:
- regel 3 – er wordt een eigenschappenbestand [properties.xml] gedefinieerd. Dit bestand definieert (sleutel, waarde)-paren. De sleutels kunnen willekeurig zijn. De waarde die bij een sleutel C hoort, wordt verkregen via de notatie ${C} in [sqlmap.config]. Hieronder volgt het bestand [properties.xml] dat aan het voorgaande bestand [sqlmap.config] zal worden gekoppeld:
regel 3 – de sleutel [provider] wordt gedefinieerd. De waarde ervan is de naam van de <provider>-tag die in [providers.config] moet worden gebruikt
regel 4 - de sleutel [connectionString] is gedefinieerd. De waarde ervan is de verbindingsstring die moet worden gebruikt om een verbinding te openen met de Firebird-gegevensbron ODBC.
- regels 4-7 – configuratieparameters:
- regel 5 - de query's SQL worden geïdentificeerd door een naam die zelf deel kan uitmaken van een naamruimte. [useStatementNamespaces="false"] geeft aan dat er geen naamruimten worden gebruikt.
- regel 6 - SqlMap beschikt over verschillende cachestrategieën om het aantal toegangen tot de gegevensbron te minimaliseren. [cacheModelsEnabled="false"] geeft aan dat er geen gebruik van zal worden gemaakt.
- regels 9-13 - hier worden de kenmerken van de gegevensbron gedefinieerd:
- regel 10 - naam van de <provider> van [providers.config] die moet worden gebruikt
- regel 11 - verbindingsstring naar de gegevensbron
- regel 12 - transactiebeheerder. Hier hebben we deze niet gebruikt, maar hebben we de regel toch laten staan omdat deze in het standaarddistributiebestand stond.
- regels 14-16 – lijst met bestanden die de uit te voeren bewerkingen SQL op de gegevensbron definiëren.
- regel 15 – definieert het mappingbestand [articles.xml]
2.5.6.4.3. articles.xml
Dit bestand heeft twee functies:
- het definiëren van een objecttoewijzing van de tabellen van de gegevensbron. In de eenvoudigste gevallen komt dit neer op het koppelen van een klasse aan een rij in een tabel.
- het definiëren van geparametriseerde bewerkingen SQL en deze een naam geven.
We zullen het volgende bestand [articles.xml] gebruiken:
Opmerkingen:
- regels 4-11 – hier wordt een mapping gedefinieerd tussen een rij van de tabel [ARTICLES] uit de gegevensbron en de klasse [istia.st.articles.dao.Article]. Aan elke kolom (column) van de tabel is een eigenschap (property) van de klasse [Article] gekoppeld. Dankzij deze mapping kan [SqlMap] het resultaat van een bewerking SQL SELECT samenstellen. Elke resultaatregel van SELECT wordt volgens de regels van de mapping in een object [Article] geplaatst.
- regel 5 - de mapping wordt omgeven door een <resultMap>-tag en krijgt een naam via het attribuut [id="article"]. De bijbehorende klasse wordt aangeduid door het attribuut [class="istia.st.articles.dao.Article"].
- regels 14-44 – de benodigde bewerkingen SQL worden gedefinieerd
- regels 16-18 – er wordt een bewerking SELECT gedefinieerd met de naam [getAllArticles]
- regel 16 – de bewerking SELECT krijgt de naam [name= "getAllArticles "] en de te gebruiken mapping wordt gedefinieerd door het attribuut [resultMap="article"]. Hier wordt dus verwezen naar de mapping van de regels 5-11
- regel 17 - tekst van de uit te voeren opdracht SQL
- regels 20-22 – hier wordt de SQL-Delete-opdracht [clearAllArticles] gedefinieerd om de artikeltabel te legen.
- regels 24-27 – hier wordt de SQL-Insert-opdracht [insertArticle] gedefinieerd om een nieuw artikel aan de artikeltabel toe te voegen. Dit is een query die wordt geparametriseerd door de elementen (#id#, #naam#, #prijs#, #huidigeVoorraad#, #minimaleVoorraad#). De waarden van deze vijf elementen zijn afkomstig van een object [Article] dat als parameter wordt doorgegeven: [parameterClass="istia.st.articles.dao.Article"]. Het parameterobject moet de eigenschappen (id, naam, prijs, huidige voorraad, minimumvoorraad) bevatten waarnaar wordt verwezen door de geconfigureerde opdracht SQL.
- regels 29-31 - hier wordt de opdracht SQL Delete [deleteArticle] gedefinieerd, bedoeld om een artikel te verwijderen waarvan het nummer #value# bekend is. Dit nummer wordt als parameter doorgegeven: [parameterClass="int"]. Dit is een algemene regel. Wanneer er slechts één parameter is, wordt deze aangeduid met het trefwoord #value# in de tekst van de opdracht SQL.
- regels 33-35 - hier wordt de SQL-Update-opdracht [modifyArticle] gedefinieerd, bedoeld om een artikel te wijzigen waarvan het nummer bekend is. Net als bij de opdracht [insertArticle] zijn de vijf benodigde gegevens afkomstig uit de eigenschappen van een object [istia.st.articles.dao.Article].
- regels 37-39 – hier wordt de SQL-Select-opdracht [getArticleById] gedefinieerd, waarmee de rij van een artikel kan worden opgehaald waarvan het nummer bekend is.
- regels 41-43 – hier wordt de SQL-Update-opdracht [changerStockArticle] gedefinieerd, waarmee het veld [stockactuel] van een artikel waarvan het nummer bekend is, wordt gewijzigd. De twee benodigde gegevens, het artikelnummer #id# en de voorraadverhoging #mouvement#, zijn te vinden in een tabel: [parameterClass="Hashtable"]. Deze tabel moet twee sleutels bevatten: id en mouvement. De waarden die aan deze twee sleutels zijn gekoppeld, worden gebruikt in de opdracht SQL.
2.5.6.4.4. Locatie van de configuratiebestanden
We zullen twee verschillende situaties bekijken:
- in het geval van een Nunit-test worden de configuratiebestanden van [SqlMap] in dezelfde map geplaatst als de geteste binaire bestanden.
- in het geval van een webapplicatie worden ze in de hoofdmap van de applicatie geplaatst.
2.5.6.5. De API van SqlMap
De klassen van SqlMap zijn opgenomen in DLL, dat doorgaans in de map [bin] van de applicatie wordt geplaatst:

Toepassingen die gebruikmaken van de klassen van SqlMap moeten de naamruimte [IBatisNet.DataMapper] importeren:
Alle bewerkingen van SQL worden uitgevoerd via een singleton van het type [Mapper], een klasse uit de naamruimte [IBatisNet.DataMapper ]. De singleton wordt als volgt verkregen:
Om de opdracht SqlMap [getAllArticles] uit te voeren, schrijft men:
- met de methode [QueryForList] kan het resultaat van een commando SELECT in een lijst worden weergegeven
- de eerste parameter is de naam van de uit te voeren opdracht SQL (zie articles.xml)
- de tweede parameter is de parameter die moet worden doorgegeven aan de aanvraag SQL. Deze moet overeenkomen met het attribuut [parameterClass] van de opdracht SqlMap. In [articles.xml] hebben we [parameterClass=Nothing]. Daarom geven we hier een nul-pointer door.
- Het resultaat is van het type IList. De objecten in deze lijst worden aangeduid door het attribuut [resultMap] van de SQL-select-opdracht: [resultMap="article"]. „article” is een mappingnaam:
De klasse die aan deze mapping is gekoppeld, is [istia.st.articles.dao.Article]. Uiteindelijk is de hierboven gedefinieerde variabele [articles] een lijst met objecten van het type [ istia.st.articles.dao.Article]. We hebben dus de volledige tabel [ARTICLES] in één instructie opgehaald. Als de tabel [ARTICLES] leeg is, krijgen we een object [IList] met 0 elementen.
Om de opdracht SqlMap [getArticleById] uit te voeren, schrijft men:
- met de methode [QueryForObject] kun je het resultaat ophalen van een commando SELECT dat slechts één regel oplevert
- de eerste parameter is de naam van de uit te voeren opdracht SqlMap
- de tweede parameter is de parameter die moet worden doorgegeven aan de aanvraag SQL. Deze moet overeenkomen met het attribuut [parameterClass] van de opdracht SqlMap. In [articles.xml] hebben we [parameterClass="int"]. Daarom geven we hier een geheel getal door dat het nummer van het gezochte artikel vertegenwoordigt.
- Het resultaat is van het type Object. Als de opdracht SELECT geen regels heeft opgeleverd, is de pointer null (nothing) als resultaat.
Om de opdracht SqlMap [insertArticle] uit te voeren, schrijft men:
- met de methode [Insert] kun je de commando’s SQL en INSERT uitvoeren
- de eerste parameter is de naam van het uit te voeren commando SqlMap
- de tweede parameter is de parameter die aan het commando moet worden doorgegeven. Deze moet overeenkomen met het attribuut [parameterClass] van het commando SqlMap. In [articles.xml] hebben we [parameterClass="istia.st.articles.dao.Article"]. Daarom geven we hier een object van het type [istia.st.articles.dao.Article] door.
Om de opdracht SqlMap [deleteArticle] uit te voeren, schrijft men:
- met de methode [Delete] kunnen de commando's SQL en DELETE worden uitgevoerd
- de eerste parameter is de naam van het uit te voeren commando SQL
- de tweede parameter is de parameter die aan het commando moet worden doorgegeven. Deze moet overeenkomen met het attribuut [parameterClass] van het commando SqlMap. In [articles.xml] hebben we [parameterClass="int"]. Daarom geven we hier het nummer van het te verwijderen artikel door.
- Het resultaat van de methode [Delete] is het aantal verwijderde regels
Op dezelfde manier schrijft men, om de opdracht SqlMap [clearAllArticles] uit te voeren, het volgende:
Om de opdracht SqlMap [modifyArticle] uit te voeren, schrijft u:
- met de methode [Update] kun je de opdrachten SQL en UPDATE uitvoeren
- de eerste parameter is de naam van het uit te voeren commando SqlMap
- de tweede parameter is de parameter die aan het commando moet worden doorgegeven. Deze moet overeenkomen met het attribuut [parameterClass] van het commando SqlMap. In [articles.xml] hebben we [parameterClass="istia.st.articles.dao.Article"]. Daarom wordt hier een object van het type [istia.st.articles.dao.Article] doorgegeven.
- Het resultaat van de methode [Update] is het aantal gewijzigde regels.
Op dezelfde manier schrijft men, om de opdracht SqlMap [changerStockArticle] uit te voeren, het volgende:
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' update
dim nbLignes as Integer= mappeur.Update("changerStockArticle", paramètres)
- de tweede parameter komt overeen met het attribuut [parameterClass] van het commando SqlMap. In [articles.xml] hebben we [parameterClass="Hashtable"]. De opdracht SQL, die is ingesteld op [changerStockArticle], gebruikt de parameters van [id, mouvement]. Daarom wordt hier een woordenboek doorgegeven met deze twee sleutels.
2.5.6.6. De code van de klasse [ArticlesDaoSqlMap]
Na de voorgaande uitleg kunnen we nu de volgende nieuwe implementatieklasse [ArticlesDaoSqlMap] schrijven:
Option Explicit On
Option Strict On
Imports System
Imports IBatisNet.DataMapper
Imports System.Collections
Namespace istia.st.articles.dao
Public Class ArticlesDaoSqlMap
Implements IArticlesDao
' privévelden
Dim mappeur As SqlMapper = Mapper.Instance
' lijst van alle artikelen
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
' een artikel toevoegen
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
SyncLock Me
Try
' unArticle: toe te voegen artikel
' invoegen
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
' verwijdert een artikel
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
SyncLock Me
Try
' id: id van het te verwijderen artikel
' verwijdering
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
' een artikel wijzigen
Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
SyncLock Me
Try
' bijwerken
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
' een artikel zoeken
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
SyncLock Me
Try
' id: id van het gezochte artikel
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
' alle artikelen verwijderen
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
' voorraad van een artikel aanpassen
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
SyncLock Me
Try
' id: id van het artikel waarvan de voorraad wordt aangepast
' beweging: voorraadbeweging
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' bijwerken
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
De lezer wordt verzocht deze code te lezen in het licht van de uitleg die is gegeven voor de API van SqlMap. Het is interessant om op te merken dat het gebruik van [SqlMap] de hoeveelheid te schrijven code aanzienlijk heeft verminderd.
2.5.6.7. Genereren van de assembly van de [dao]-laag
Het nieuwe Visual Studio-project heeft de volgende structuur:

Let op de aanwezigheid van de "assembly's" die nodig zijn voor SqlMap in de projectverwijzingen. Deze DLL zijn in de map [bin] van het project geplaatst. Het project is geconfigureerd om een DLL te genereren met de naam [webarticles-dao.dll]:
![]() | ![]() |
2.5.6.8. NUnit-tests van de laag [dao]
2.5.6.8.1. De testklasse NUnit
De Nunit-testklasse van de implementatieklasse [ArticlesDaoSqlMap] is dezelfde als die van de klasse [ArticlesDaoPlainODBC] (zie paragraaf 2.3.3.2). We volgen een vergelijkbare werkwijze om de Nunit-test voor de klasse [ArticlesDaoSqlMap] voor te bereiden:
- we maken in de Visual Studio-map van het project [dao-sqlmap] de map [test1] (rechts) aan door de map [tests] uit het project [dao-odbc] (links) te kopiëren:
![]() | ![]() |
- in de map [tests] vervangen we de bestanden DLL en [webarticles-dao.dll] door de bestanden DLL en [webarticles-dao.dll] die afkomstig zijn van de generatie van het project [dao-sqlmap].
- We voegen de benodigde DLL-bestanden toe aan SqlMap, evenals de onderzochte configuratiebestanden [providers.config, sqlmap.config, properties.xml, articles.xml].
- We passen het configuratiebestand [spring-config.xml] aan om de nieuwe klasse [ArticlesDaoSqlMap] te instantiëren:
Opmerkingen:
- regel 7: het object [articlesdao] is nu gekoppeld aan een instantie van de klasse [ArticlesDaoSqlMap]
- deze klasse heeft geen constructor. Er wordt de standaardconstructor gebruikt.
2.5.6.8.2. Tests
De tabel [ARTICLES] van de Firebird-gegevensbron is gevuld met de volgende items:

We zijn klaar om te testen. Met behulp van de applicatie [Nunit-Gui] laden we de DLL en [test-webarticles-dao.dll] uit de bovenstaande map [test1] en voeren we de test [testGetAllArticles] uit:

Ondanks de naam [NUnitTestArticlesDaoArrayList] die aanvankelijk aan de testklasse was gegeven, wordt hier inderdaad de klasse [ArticlesDaoSqlMap] getest. De schermafbeelding laat zien dat we de artikelen die we in de tabel [ARTICLES] hadden geplaatst, correct hebben opgehaald. Laten we nu alle tests uitvoeren:

De lezer die dit document op het scherm bekijkt, zal zien dat sommige tests zijn geslaagd (groene kleur), maar dat andere zijn mislukt (rode kleur). De tests die zijn mislukt, zijn de tests [testArticleAbsent] en [testChangerStockArticle]. Na uitgebreid onderzoek lijkt het erop dat de oorzaken van deze mislukkingen de volgende zijn:
- in [testArticleAbsent] wordt gevraagd om een artikel te wijzigen dat niet bestaat. Hiervoor wordt de methode [modifieArticle] gebruikt, die het aantal gewijzigde regels op 0 of 1 zet. Hier zou het 0 moeten zijn. In plaats daarvan krijgen we een uitzondering van het type [IBatisNet.Common.Exceptions.ConcurrentException].
- In [changerStockArticle] hebben we opnieuw een bewerking van het type [update]. Het gaat hier om het verminderen van een voorraad met een hoeveelheid die groter is dan de voorraad zelf. Hiervoor wordt de methode [changerStockArticle] gebruikt, die het aantal gewijzigde regels weergeeft, dus 0 of 1. De opdracht SQL is geschreven om een update te voorkomen (zie opdracht SQL „changerStockArticle“ in articles.xml) die de voorraad negatief zou maken. Hier verwachten we 0 als resultaat van de methode [changerStockArticle]. Opnieuw krijgen we een uitzondering van het type [IBatisNet.Common.Exceptions.ConcurrentException].
Er zijn talrijke mogelijke oorzaken voor deze fout:
- de code van de klasse [ArticlesDaoSqlMap] is foutief. Dat is mogelijk. Deze is echter afkomstig van een port van een Java-klasse die correct had gewerkt met de Java-versie van SqlMap.
- de .NET-versie van SqlMap bevat een bug
- de Firebird-driver ODBC bevat fouten
- ...
Bij gebrek aan zekerheid gaan we dit probleem omzeilen door de bekende uitzondering [IBatisNet.Common.Exceptions.ConcurrentException] op te vangen. De nieuwe code van de klasse [ArticlesDaoSqlMap] ziet er als volgt uit:
De wijzigingen staan op de regels: 28, 41, 69. Voor bewerkingen van het type SQL van het type [UPDATE, DELETE] geldt dat, als er een uitzondering van het type [IBatisNet.Common.Exceptions.ConcurrentException] optreedt, 0 als resultaat wordt geretourneerd, waarmee wordt aangegeven dat er geen regels zijn gewijzigd of verwijderd. Vervolgens wordt de DLL van het project opnieuw gegenereerd, in de map [test1] geplaatst en worden de NUnit-tests opnieuw uitgevoerd:

Deze keer is het gelukt. We gaan voortaan met deze DLL werken.
2.5.6.9. Integratie van de nieuwe laag [dao] in de applicatie [webarticles]
2.5.6.9.1. gegevensbron ODBC
We testen hier de gegevensbron ODBC die in paragraaf 2.3.3.1 is besproken. Deze wordt hier gebruikt via SqlMap.
We volgen de werkwijze uit paragraaf 2.3.4. We brengen de volgende wijzigingen aan in de inhoud van de map [runtime]:
- in het dossier [bin] wordt de DLL van deoude laag [dao] wordt vervangen door de DLL van de nieuwe laag [dao], geïmplementeerd door de klasse [ArticlesDaoSqlMap]. We voegen hier de voor Firebird benodigde DLL en SqlMap toe:

- in [runtime] plaatsen we de configuratiebestanden van SqlMap en [providers.config, sqlmap.config, properties.xml, articles.xml]:

- in [runtime] wordt het configuratiebestand [web.config] vervangen door een bestand dat rekening houdt met de nieuwe implementatieklasse:
Opmerkingen:
- op regel 14 wordt aan de singleton [articlesDao] een instantie van de nieuwe klasse [ArticlesDaoSqlMap] gekoppeld. Dit is de enige wijziging.
We zijn klaar om te testen. We configureren de webserver [Cassini] zoals in de vorige tests. We initialiseren de artikeltabel met de volgende waarden:

Met een browser roepen we de pagina URL [http://localhost/webarticles/main.aspx] op:

![]() |
Laten we nu de inhoud van de tabel [ARTICLES] bekijken:

De artikelen [couteau] en [cuiller] zijn aangekocht en de voorraad is met de aangekochte hoeveelheid verminderd. Het artikel [fourchette] kon niet worden aangekocht omdat de gevraagde hoeveelheid groter was dan de voorraad. We nodigen de lezer uit om aanvullende tests uit te voeren.
2.5.6.9.2. gegevensbron MSDE
We testen hier de gegevensbron MSDE die in paragraaf 2.4.3.1 is besproken. Deze wordt hier gebruikt via SqlMap. We volgen dezelfde werkwijze als eerder. We brengen de volgende wijzigingen aan in de inhoud van het bestand [runtime]:
- de inhoud van de map [bin] blijft ongewijzigd
- in [runtime] worden de configuratiebestanden van SqlMap en [providers.config, properties.xml] gewijzigd. De configuratiebestanden van [sqlmap.config, articles.xml] blijven ongewijzigd.
- Het bestand [providers.config] configureert een nieuwe <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>
Deze <provider> maakt gebruik van de klassen .NET voor toegang tot de gegevensbronnen van de SQL-server. Deze is standaard geïntegreerd in het sjabloonbestand [providers.config] dat wordt meegeleverd met SqlMap.
- Het bestand [properties.xml] definieert de <provider> van de bron MSDE en de bijbehorende verbindingsstring:
<?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] blijft het configuratiebestand [web.config] ongewijzigd.
We zijn klaar om te testen. De webserver [Cassini] behoudt zijn gebruikelijke configuratie. We initialiseren de artikeltabel van de bron MSDE met [EMS MS SQL Manager]:

Met een browser vragen we de URL [http://localhost/webarticles/main.aspx] op:

![]() |
Laten we nu de inhoud van de tabel [ARTICLES] vergelijken met die van [EMS MS SQL Manager]:

De artikelen [ballon foot] en [raquette tennis] zijn aangekocht en de voorraad is met de aangekochte hoeveelheid verminderd. Het artikel [rollers] kon niet worden aangekocht omdat de gevraagde hoeveelheid groter was dan de voorraad. We nodigen de lezer uit om aanvullende tests uit te voeren.
2.5.6.9.3. gegevensbron OleDb
We testen hier de gegevensbron ACCESS die in paragraaf 2.4.5.1 is beschreven. Deze wordt hier gebruikt via SqlMap. We volgen dezelfde werkwijze als eerder. We brengen de volgende wijzigingen aan in de inhoud van het bestand [runtime]:
- de inhoud van de map [bin] blijft ongewijzigd
- in [runtime] worden de configuratiebestanden van SqlMap en [providers.config, properties.xml] gewijzigd. De configuratiebestanden van [sqlmap.config, articles.xml] blijven ongewijzigd.
- Het bestand [providers.config] configureert een nieuwe <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>
Deze <provider> maakt gebruik van de klassen .NET voor toegang tot de gegevensbronnen OleDb. Deze is standaard geïntegreerd in het sjabloonbestand [providers.config] dat samen met SqlMap wordt geleverd.
- Het bestand [properties.xml] definieert de <provider> van de bron OleDb en de bijbehorende verbindingsstring:
<?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] blijft het configuratiebestand [web.config] ongewijzigd.
We zijn klaar om te testen. De webserver [Cassini] behoudt zijn gebruikelijke configuratie. We initialiseren de artikeltabel van de bron ACCESS als volgt:

Met een browser vragen we de pagina URL [http://localhost/webarticles/main.aspx] op:

![]() |
Laten we nu de inhoud van de tabel [ARTICLES] controleren met:

De artikelen [pantalon] en [jupe] zijn aangekocht en de voorraad is met de aangekochte hoeveelheid verminderd. Het artikel [manteau] kon niet worden aangekocht omdat de gevraagde hoeveelheid groter was dan de voorraad. We nodigen de lezer uit om aanvullende tests uit te voeren.
2.5.7. Conclusie
Hiermee sluiten we dit uitgebreide tutorialartikel af. Wat hebben we gedaan?
- We hebben de laag [dao] van een webapplicatie met drie lagen op vier verschillende manieren geïmplementeerd:
- met behulp van de toegangsklassen .NET voor de ODBC-bronnen
- met behulp van de .NET-toegangsklassen voor de SQL Server-bronnen
- met behulp van de toegangsklassen .NET voor de bronbestanden OleDb
- met behulp van toegangsklassen van een derde partij om toegang te krijgen tot een Firebird-database
- telkens hebben we de nieuwe laag [dao] geïntegreerd in de drielaagse applicatie [webarticles] [web, domain, dao] zonder dat een van de lagen [web, domain] opnieuw gecompileerd hoefde te worden
- ten slotte hebben we de tool [SqlMap] geïntroduceerd, waarmee we een laag [dao] konden creëren die zich op een voor de code transparante manier aan verschillende gegevensbronnen kan aanpassen. Zo konden we met deze nieuwe laag achtereenvolgens de gegevensbronnen van de voorgaande implementaties 1 tot en met 3 gebruiken. Dit is op transparante wijze gerealiseerd met behulp van configuratiebestanden.
- We hebben aangetoond hoe flexibel de Spring- en SqlMap-tools zijn voor drielaagse webapplicaties.































































