2. Частина 2
2.1. Introduction
Спочатку нагадаємо, що було зроблено в частині 1, зокрема, про використану трирівневу архітектуру [web, domain, dao]. У запропонованому рішенні рівень [dao] був тестовим: джерело даних реалізовувалося об’єктом [ArrayList]. У цій статті ми зупинимося на шарі [dao], представивши різні можливі варіанти його реалізації, коли дані містяться в SGBD.
Використані інструменти:
- SGBD Firebird — див. додаток, параграф 3.5.
- SGBD MSDE (Microsoft Data Engine) — див. додаток, пункт 3.12.
- IBExpert, персональна версія для графічного адміністрування SGBD Firebird — див. додаток, пункт 3.6.
- EMS MS SQL Manager для графічного адміністрування SGBD MSDE - див. додаток, пункт 3.14.
- Ibatis SqlMap для рівня доступу до даних SGBD — див. пункт 2.5.6.2.
За шкалою «початковий — середній — просунутий» цей документ належить до розділу [intermédiaire-avancé]. Для його розуміння необхідні певні попередні знання. Деякі з них можна отримати з документів, які я написав. У такому випадку я наводжу посилання на них. Зрозуміло, що це лише рекомендація, і читач може користуватися своїми улюбленими документами.
- мова VB.net: [Introduction au langage VB.NET par l'exemple ]
- веб-програмування на VB.net: [Développement WEB avec ASP.NET 1.1 ]
- використання компонента IoC у Spring: [Spring IoC pour .NET ]
- документація Ibatis SqlMap: [http://prdownloads.sourceforge.net/ibatisnet/DevGuide.pdf?download]
- документація Firebird: [http://firebird.sourceforge.net/pdfmanual/Firebird-1.5-QuickStart.pdf]
- документація Spring.net: [http://www.springframework.net/documentation.html]
2.2. Веб-додаток «articles» — нагадування
Тут ми представляємо елементи спрощеного веб-додатку для електронної комерції, розглянутого в частині 1. Цей додаток дозволяє веб-користувачам:
- переглядати перелік товарів із бази даних
- додавати деякі з них до електронного кошика
- підтвердити його. Єдиним наслідком такого підтвердження є оновлення в базі даних запасів придбаних товарів.
2.2.1. Види додатка
Користувачеві доступні такі екрани:
![]() |
![]() |
![]() |
- екран [ERREURS], який повідомляє про будь-які помилки в роботі програми

2.2.2. Загальна архітектура додатка
Додаток, створений у частині 1, має трирівневу архітектуру:
![]() |
- три рівні стали незалежними завдяки використанню інтерфейсів
- інтеграція різних рівнів була здійснена за допомогою Spring
- кожен рівень має окремий простір імен: web (рівень UI), domain (бізнес-рівень) та dao (рівень доступу до даних).
Додаток відповідає архітектурі MVC (Модель — Представлення — Контролер). Якщо повернутися до наведеної вище схеми шарів, архітектура MVC вписується в неї наступним чином:
![]() |
Обробка запиту клієнта відбувається за такими етапами:
- клієнт надсилає запит до контролера. Цей контролер у даному випадку — це сторінка .aspx, якій відведено особливу роль. Вона обробляє всі запити клієнтів. Це вхідні ворота додатка. Це літера «C» у MVC.
- контролер обробляє цей запит. Для цього йому може знадобитися допомога бізнес-шару, який у структурі MVC називається моделлю M.
- Контролер отримує відповідь від бізнес-шару. Запит клієнта було оброблено. Це може призвести до кількох можливих відповідей. Класичним прикладом є
- сторінка з повідомленням про помилку, якщо запит не вдалося обробити належним чином
- сторінка підтвердження в іншому випадку
- контролер обирає відповідь (= представлення), яку потрібно надіслати клієнту. Найчастіше це сторінка, що містить динамічні елементи. Контролер передає їх до представлення.
- Вигляд надсилається клієнту. Це «V» у MVC.
2.2.3. Шаблон
Модель M у MVC складається з таких елементів:
- бізнес-класи
- класи доступу до даних
- база даних
2.2.3.1. База даних
База даних містить лише одну таблицю з назвою ARTICLES, створену за допомогою таких команд SQL:
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
);
/* обмеження */
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);
/* первинний ключ */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
первинний ключ, що однозначно ідентифікує товар | |
назва товару | |
її ціна | |
поточний запас | |
запас, при недосягненні якого необхідно оформити замовлення на поповнення запасів |
2.2.3.2. Простори імен моделі
Модель M надається у вигляді двох просторів імен:
- istia.st.articles.dao: містить класи доступу до даних шару [dao]
- istia.st.articles.domain: містить бізнес-класи рівня [domain]
Кожен із цих просторів імен міститься у власному файлі «assembly»:
assembly | contenu | rôle |
webarticles-dao | - [IArticlesDao]: інтерфейс доступу до шару [dao]. Це єдиний інтерфейс, який бачить шар [domain]. Він не бачить жодного іншого. - [Article]: клас, що визначає статтю - [ArticlesDaoArrayList]: клас реалізації інтерфейсу [IArticlesDao] із класом [ArrayList] | шар доступу до даних — повністю знаходиться в шарі [dao] трирівневої архітектури веб-додатку |
webarticles-domain | - [IArticlesDomain]: інтерфейс доступу до шару [domain]. Це єдиний інтерфейс, який бачить веб-шар. Інших він не бачить. - [AchatsArticles]: клас, що реалізує [IArticlesDomain] - [Achat]: клас, що представляє покупку клієнта - [Panier]: клас, що представляє сукупність покупок клієнта | представляє модель покупок у мережі — повністю розміщується в шарі [domain] трирівневої архітектури веб-додатку |
2.2.4. Розгортання та тестування додатка [webarticles]
2.2.4.1. Розгортання
Ми розгортаємо додаток, розроблений у частині 1 статті, у папці з назвою [runtime]:
![]() | ![]() |
![]() |
Коментарі:
Папка [runtime] містить три файли та дві підпапки:
- контролери [global.asax] та [main.aspx]
- файл конфігурації [web.config]
- папка [bin], яка містить:
- файли DLL трьох рівнів: [webarticles-dao.dll], [webarticles-domain.dll], [webarticles-web.dll]
- необхідні для Spring файли [Spring-Core.*], [log4net.dll]
- папка [vues], що містить код для відображення різних видів.
- Наявність файлів коду .vb не потрібна, оскільки їхня скомпільована версія міститься у DLL.
2.2.4.2. Тестування
Ми налаштовуємо веб-сервер [Cassini] наступним чином:

з:
Фізичний шлях: D:\data\serge\travail\2004-2005\aspnet\webarticles-010405\runtime\
Віртуальний шлях: /webarticles
За допомогою браузера ми звертаємося до URL [http://localhost/webarticles/main.aspx]

Нагадаємо, що рівень [dao] реалізовано класом, який зберігає статті в об’єкті [ArrayList]. Цей клас створює початковий список із чотирьох статей. Виходячи з наведеного вище зображення, ми використовуємо посилання меню для виконання операцій. Ось декілька з них. Лівий стовпець відображає запит клієнта, а правий — відповідь, яку йому надано.
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
2.2.5. Перероблений рівень [dao]
У нашій першій реалізації шару [dao] інтерфейс доступу до даних [IArticlesDao] було реалізовано за допомогою класу, що зберігав статті в об’єкті [ArrayList]. Це дозволило нам не заглиблюватися в деталі цього рівня та продемонструвати, що важливим є лише його інтерфейс, а не реалізація. Таким чином, нам вдалося створити робочий веб-додаток. Він складається з трьох рівнів: [web], [domain] та [dao]. Тут ми запропонуємо різні реалізації шару [dao]. Кожна з них зможе замінити поточний рівень [dao] без жодних змін у рівнях [domain] та [web]. Така гнучкість досягається завдяки тому, що:
- шар [domain] звертається не до конкретного класу, а до інтерфейсу [IArticlesDao]
- завдяки Spring ми змогли приховати від шару [domain] ім’я класу, що реалізує інтерфейс [IArticlesDao].
2.2.5.1. Елементи шару [dao]
Нагадаємо деякі елементи шару [dao], які будуть збережені в нових реалізаціях:
- - [IArticlesDao]: інтерфейс доступу до шару [dao]
- - [Article]: клас, що визначає статтю
2.2.5.2. Клас [Article]
Клас, що визначає статтю, має такий вигляд:
Imports System
Namespace istia.st.articles.dao
Public Class Article
' приватні поля
Private _id As Integer
Private _nom As String
Private _prix As Double
Private _stockactuel As Integer
Private _stockminimum As Integer
' ідентифікатор статті
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
' назва товару
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
' ціна товару
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
' поточний запас товару
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
' мінімальний запас товару
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
' виробник за замовчуванням
Public Sub New()
End Sub
' виробник із властивостями
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
' метод ідентифікації товару
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
Цей клас надає:
- конструктор, що дозволяє задати 5 параметрів статті: [id, nom, prix, stockactuel, stockminimum]
- публічні властивості, що дозволяють читати та записувати ці 5 елементів інформації.
- перевірка даних, введених у товар. У разі виявлення помилкових даних генерується виняток.
- метод toString, який дозволяє отримати значення статті у вигляді символьного рядка. Це часто буває корисним для налагодження програми.
2.2.5.3. Інтерфейс [IArticlesDao]
Інтерфейс [IArticlesDao] визначено наступним чином:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' перелік усіх товарів
Function getAllArticles() As IList
' додає товар
Function ajouteArticle(ByVal unArticle As Article) As Integer
' видаляє товар
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' змінює товар
Function modifieArticle(ByVal unArticle As Article) As Integer
' пошук товару
Function getArticleById(ByVal idArticle As Integer) As Article
' видалити всі товари
Sub clearAllArticles()
' змінює запас товару
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
Роль різних методів інтерфейсу така:
повертає всі записи з джерела даних | |
очищає джерело даних | |
повертає об’єкт [Article], ідентифікований за номером | |
дозволяє додати товар до джерела даних | |
дозволяє змінити товар у джерелі даних | |
дозволяє видалити товар із джерела даних | |
дозволяє змінити залишок товару у джерелі даних |
Інтерфейс надає клієнтським програмам певну кількість методів, визначених виключно їхніми сигнатурами. Він не визначає, як ці методи будуть фактично реалізовані. Це забезпечує гнучкість у роботі додатка. Клієнтська програма звертається до інтерфейсу, а не до його конкретної реалізації.
![]() |
Вибір конкретної реалізації здійснюється за допомогою конфігураційного файлу Spring.
2.3. Клас реалізації [ArticlesDaoPlainODBC]
Ми пропонуємо нову реалізацію шару [dao], яка передбачає, що дані знаходяться у джерелі ODBC. Відомо, що в Windows майже всі SGBD, що є на ринку, мають драйвер ODBC. Перевага цього рішення полягає в тому, що можна змінювати SGBD, не впливаючи на роботу програми. Недоліком є те, що драйвер ODBC, який використовує лише спільні для всіх SGBD функції, зазвичай менш ефективний, ніж драйвер, спеціально написаний для використання всього потенціалу конкретного SGBD. Приклад створення коду для ODBC можна знайти в розділі 3.7.
2.3.1. Код
2.3.1.1. Скелет
Клас [ArticlesDaoPlainODBC] реалізує інтерфейс [IArticlesDao] наступним чином:
Коментарі:
- у рядку 3 імпортується простір імен, що містить класи .NET для доступу до джерел ODBC
- у рядку 11 — зберігається з’єднання з джерелом ODBC
- рядок 12 — зберігає ім’я DSN джерела даних
- рядки 13–19 — приватні змінні типу [OdbcCommand], що визначають запити SQL, які використовуються різними методами класу
- рядки 22–27 — конструктор. Він отримує елементи, які дозволяють йому створити об’єкт [OdbcConnection], що пов’яже код із джерелом даних ODBC
- рядки 29–31 — метод додавання товару
- рядки 33–35 — метод зміни запасів товару
- рядки 37–39 — метод, що видаляє всі товари з джерела даних ODBC
- рядки 41–43 — метод, що отримує список усіх товарів із джерела ODBC
- рядки 45–47 — метод, що дозволяє отримати конкретний товар
- рядки 49–51 — метод, що дозволяє змінити певні поля товару, номер якого відомий
- рядки 53–55 — метод, що дозволяє видалити товар за номером
- рядки 57–60 — допоміжний метод, що дозволяє виконати [SELECT] над джерелом даних і повернути результат
- рядки 62–64 — допоміжний метод, що дозволяє виконати [INSERT, UPDATE, DELETE] над джерелом даних і повернути результат
2.3.1.2. Конструктор
Коментарі:
- рядок 2 — конструктор отримує три параметри, необхідні для підключення до джерела ODBC: ім’я джерела DSN, ідентифікатор для підключення та відповідний пароль.
- рядок 8 — ім’я джерела DSN зберігається, щоб можна було вказати його у повідомленнях про помилки.
- рядок 9 — створюється екземпляр об’єкта [OdbcConnection]. Створений екземпляр з’єднання не є відкритим з’єднанням. Відкриття з’єднання здійснює метод [open].
- рядки 12–19 — готуємо запити SQL в об’єктах [OdbcCommand]. Це дозволить нам уникнути їхнього повторного формування щоразу, коли вони знадобляться. Формальні параметри ? запитів будуть замінені на фактичні значення під час виконання запиту.
2.3.1.3. Метод executeQuery
Коментарі:
- метод [executeQuery] — це допоміжний метод, який:
- виконує запит [SELECT id, nom, prix, stockactuel, stockminimum from ARTICLES ...] до джерела даних
- повертає результат у вигляді списку об’єктів [Article]
- рядок 1 — єдиним параметром методу є об’єкт [OdbcCommand], що містить запит [Select], який потрібно виконати.
- рядок 7 — з’єднання відкрито. Воно буде закрито в рядку 29 незалежно від того, чи сталася помилка.
- рядок 9 — створюється екземпляр об’єкта [OdbcDataReader], необхідного для обробки результату [Select]
- рядки 13–23 — кожен рядок результату [Select] поміщається в об’єкт [Article], який приєднується до інших елементів у об’єкті [ArrayList]
- список статей повертається у рядку 25
- винятки не обробляються. Їх має обробляти код, що викликає цей метод.
2.3.1.4. Метод executeUpdate
Коментарі:
- метод отримує об’єкт [OdbcCommand], який містить запит SQL типу [Insert, Update, Delete].
- З'єднання відкривається у рядку 5. Воно закривається у рядку 10 незалежно від того, чи стався виняток.
- Запит на оновлення виконується у рядку 7. Результат — кількість рядків таблиці ARTICLES, змінених запитом, — повертається негайно.
2.3.1.5. Метод ajouteArticle
Коментарі:
- рядок 1 — метод отримує статтю, яку потрібно додати до джерела даних ODBC. Він повертає кількість рядків, на які вплинула ця операція, c.a.d. 1 або 0
- рядки 3 і 20 — метод синхронізовано. Це стосується всіх методів доступу до даних. Це означає, що одночасно з джерелом даних може працювати лише один потік. Це, ймовірно, занадто консервативний підхід. Існують кращі альтернативи, зокрема включення цих операцій у транзакції. У цьому випадку паралельний доступ керує об’єкт SGBD. Ми не хотіли вводити поняття транзакції на цьому етапі. Spring надає нам можливість впровадити їх у рівень [domain]. Можливо, ми повернемося до цього питання в іншій статті.
- У рядках 5–12 присвоюються значення формальним параметрам запиту об’єкта [insertCommand], ініціалізованого конструктором. Нагадаємо його:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
5 значень, необхідних для запиту, надаються у рядках 7–11.
- У рядках 13–19 виконується запит. Якщо все проходить успішно, повертається результат. В іншому випадку генерується загальне виключення з чітким повідомленням про помилку
2.3.1.6. Метод modifieArticle
Коментарі:
- рядок 1 — метод отримує статтю, яку потрібно змінити, із джерела даних ODBC. Він повертає кількість рядків, на які впливає ця операція, c.a.d. 1 або 0
- коментарі до методу [ajouteArticle] можна навести тут
2.3.1.7. Метод supprimeArticle
Коментарі:
- рядок 1 — метод отримує номер товару, який потрібно видалити, із джерела даних ODBC. Він повертає кількість рядків, на які впливає ця операція, c.a.d. 1 або 0
- коментарі до методу [ajouteArticle] можна навести тут
2.3.1.8. Метод getAllArticles
Коментарі:
- рядок 1 — метод не приймає жодних параметрів. Він повертає список усіх статей із джерела даних ODBC
- запит [Select], що вимагає всіх статей, передається методу [executeQuery] — рядок 6
- отриманий список повертається у рядку 8
- у рядках 9–12 обробляється можливе виключення
2.3.1.9. Метод getArticleById
Коментарі:
- рядок 1 — метод отримує як параметр номер потрібного товару. Він повертає цей номер, якщо товар знайдено у джерелі ODBC, інакше повертає посилання [nothing].
- запит [Select], що запитує товар, ініціалізується у рядках 5–8
- він виконується у рядку 12 — отримуємо список статей
- якщо цей список порожній, у рядку 14 повертається посилання [nothing]
- інакше єдиний товар зі списку повертається у рядку 16
- у рядках 17–20 обробляється можливе виключення
2.3.1.10. Метод clearAllArticles
Коментарі:
- рядок 1 — метод не приймає жодних параметрів і нічого не повертає
- рядок 6 — виконується запит на видалення всіх статей
- рядки 7–10 — обробляється можливе виключення
2.3.1.11. Метод changerStockArticle
Коментарі:
- рядок 1 — метод отримує як параметри номер артикулу, запас якого потрібно змінити, а також величину зміни запасу (додатну або від’ємну). Він повертає кількість рядків, змінених операцією c.a.d. 0 або 1.
- рядки 5–10 — ініціалізується запит [updateStockCommand]. Нагадаємо текст запиту SQL:
updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
Слід зауважити, що запас змінюється лише в тому випадку, якщо після зміни він залишається >=0.
- Запит на оновлення запасів товару виконується у рядку 13, і результат виводиться
- у рядках 14–18 обробляється можливий виняток
2.3.2. Генерація збірки шару [dao]
Проєкт Visual Studio цієї нової версії шару [dao] має таку структуру:

Проєкт налаштовано на генерацію DLL під назвою [webarticles-dao.dll]:
![]() | ![]() |
2.3.3. Тести Nunit для шару [dao]
2.3.3.1. Створення джерела даних ODBC-Firebird
Для тестування нашого нового шару [dao] нам потрібне джерело даних ODBC, а отже, і база даних. Ми використовуємо Firebird SGBD (розділ 3.5). За допомогою IBExpert (розділ 3.6) ми створюємо таку базу товарів:
![]() | ![]() |
Адміністратором цієї бази даних буде користувач [SYSDBA] з паролем [masterkey]. Створюємо кілька статей:

Тепер створюємо наступне джерело Firebird ODBC (див. розділ 3.7):
![]() |
Створений файл джерела ODBC має такі характеристики:
- ім’я DSN: odbc-firebird-articles
- ідентифікатор підключення: SYSDBA
- відповідний пароль: masterkey
2.3.3.2. Тестовий клас NUnit
Ми вже написали тестовий клас для початково створеного шару [dao]. Як, мабуть, пам’ятає читач, цей клас тестував не конкретний клас, а інтерфейс [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
' об’єкт, що тестується
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' отримуємо екземпляр Spring-генератора об’єктів
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' запитуємо інстанцію об’єкта articlesdao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
....
Як бачимо, у методі атрибута <Setup()> Spring запитується посилання на синглтон із назвою [articlesdao] типу [IArticlesDao], тобто типу інтерфейсу. Сінглтон [articlesdao] був визначений у такому файлі конфігурації [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>
Переконаємося, що початковий тестовий клас дозволяє нам протестувати наш новий шар [dao] без змін та перекомпіляції.
- Створимо у папці Visual Studio нашого нового шару [dao] папку [tests] (праворуч нижче), скопіювавши папку [bin] з тестового проєкту початкового шару [dao] (ліворуч нижче). За потреби читачеві пропонується ознайомитися з тестовим проектом першої версії шару [dao] у першій частині статті.
![]() | ![]() |
- у папці [tests] замінюємо DLL та [webarticles-dao.dll], отримані зстарого шару [dao] на DLL та [webarticles-dao.dll], що походять з нового шару [dao]
- змінимо файл конфігурації [spring-config.xml], щоб створити екземпляр нового класу [ArticlesDaoPlainODBC]:
Коментарі:
- у рядку 6 об’єкт [articlesdao] тепер пов’язаний з екземпляром класу [ ArticlesDaoPlainODBC]
- цей клас має конструктор із трьома аргументами:
- ім’я джерела DSN — рядок 8
- ідентифікатор, за допомогою якого буде здійснюватися доступ до бази даних — рядок 11
- пароль, пов'язаний із цим ідентифікатором — рядок 14
Тут ми використовуємо дані з джерела ODBC-Firebird, яке ми створили раніше.
2.3.3.3. Тестування
Тепер ми готові до тестування. За допомогою програми [Nunit-Gui] завантажуємо DLL та [test-webarticles-dao.dll] із папки [tests], зазначеної вище, і виконуємо тест [testGetAllArticles]:

Дивлячись на знімок екрана вище, можна пошкодувати про назву [NUnitTestArticlesDaoArrayList], яку спочатку було надано класу тестування. Це вводить в оману. Адже саме клас [ArticlesDaoPlainODBC] тут і тестується. Знімок екрана показує, що ми правильно витягли записи, які розмістили в таблиці [ARTICLES]. Тепер виконаємо всі тести:

У лівому вікні відображається список протестованих методів. Колір крапки перед назвою кожного методу вказує на успішне виконання (зелений) або невдале виконання (червоний) методу. Читач, який переглядає цей документ на екрані, побачить, що всі тести пройшли успішно.
2.3.3.4. Висновок
Ми щойно продемонстрували, що:
- оскільки тестовий клас NUnit посилався не на клас, а на інтерфейс;
- оскільки точна назва класу, що інстанціює інтерфейс, була вказана у файлі конфігурації, а не в коді;
- оскільки Spring сам створював екземпляр класу та надавав посилання на нього тестовому коду;
тож тестовий код, написаний для початкового шару [dao], залишався дійсним для нової реалізації цього самого шару. Нам не знадобився доступ до коду тестового класу. Ми використовували лише його скомпільовану версію, згенеровану під час тестування початкового шару [dao]. Ми зробимо аналогічні висновки, коли знадобиться інтегрувати новий шар [dao] у додаток [webarticles].
2.3.4. Інтеграція нового шару [dao] у додаток [webarticles]
2.3.4.1. Тестування інтеграції
Нагадаємо, що початкова версія додатка [webarticles] була розгорнута в наступній папці [runtime]:
![]() | ![]() |
![]() |
Читачеві пропонується, за необхідності, переглянути розділ 2.2.4, де детально описано порядок розгортання додатка [webarticles]. Ми вносимо такі зміни до вмісту папки [runtime]:
- у папці [bin] файл DLL зі старого шару [dao] замінено на файл DLL з нового шару [dao]
- у [runtime] файл конфігурації [web.config] замінюється файлом, який враховує новий клас реалізації шару [dao]:
![]() |
![]() |
Новий файл конфігурації [web.config] має такий вигляд:
Коментарі:
- у рядках 14–24 до синглтона [articlesDao] прив’язується екземпляр нового класу [ArticlesDaoPlainODBC]. Це єдина зміна. Ми вже стикалися з нею під час тестування нового шару [dao].
Ми готові до тестування. Налаштовуємо веб-сервер [Cassini] так само, як описано в розділі 2.2.4. Ініціалізуємо таблицю статей [Firebird] такими значеннями:

Переконайтеся, що веб-сервер Cassini, а також SGBD і [Firebird] запущені. У браузері вводимо URL-адресу [http://localhost/webarticles/main.aspx]:

![]() |
Тепер перевіримо вміст таблиці [ARTICLES] у базі даних [Firebird]:

Товари [parapluie] та [bottes] було придбано, а їхні запаси зменшено на кількість, що була придбана. Товар [chapeau] не вдалося придбати, оскільки запитувана кількість перевищувала кількість на складі. Пропонуємо читачеві провести додаткові тести.
2.3.4.2. Висновок
Що ми зробили?
- ми відновили робочу версію попередньої версії;
- ми замінили DLL з шару [dao] на нову версію. DLL шарів [web] та [domain] залишилися без змін;
- ми змінили файл конфігурації [web.config], щоб він враховував новий клас реалізації шару [dao]
Все це забезпечує чіткість коду та значну гнучкість у розвитку веб-додатку. Ці важливі характеристики забезпечуються двома архітектурними рішеннями:
- доступ до шарів через інтерфейси
- інтеграція та конфігурація шарів за допомогою Spring.
Зараз ми пропонуємо нову реалізацію шару [dao].
2.4. Клас реалізації [ArticlesDaoSqlServer]
Друга реалізація шару [dao] передбачає, що дані знаходяться в базі даних SQL Server. Microsoft надає SGBD під назвою MSDE, який є обмеженою версією сервера SQL. Інформацію про те, як його отримати та встановити, наведено в додатку, параграф 3.12.
2.4.1. Код
Клас [ArticlesDaoSqlServer] дуже схожий на клас [ArticlesDaoPlainODBC], який ми розглядали раніше. Тому ми вкажемо лише зміни, внесені порівняно з попередньою версією:
- необхідні класи знаходяться в просторі імен [System.Data.SqlClient] замість простору імен [System.Data.Odbc]
- з’єднання типу [OdbcConnection] тепер має тип [SqlConnection]
- об’єкти [OdbcCommand] тепер мають тип [SqlCommand]
- синтаксис параметризованих запитів SQL змінюється. Запит на вставку тепер має такий вигляд:
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
тоді як раніше він мав вигляд:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
- метод [ajouteArticle] тепер виглядає так:
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' ексклюзивний блок
SyncLock Me
' підготовка запиту на вставку
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
'— виконується
Return executeUpdate(insertCommand)
Catch ex As Exception
'помилка запиту
Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
End Try
End SyncLock
End Function
- виробник також змінено:
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' сервер: ім'я екземпляра SQL, до якого потрібно підключитися
' databaseName: ім'я бази даних, до якої потрібно підключитися
' uid: ідентифікатор користувача
' password: його пароль
'отримуємо ім’я бази даних, передане як аргумент
Me.databaseName = databaseName
'— створюється екземпляр з'єднання
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' готуємо запити SQL
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
...
End Sub
Тепер конструктор приймає чотири параметри:
' сервер: ім’я екземпляра SQL, до якого слід підключитися
' databaseName: назва бази даних, до якої потрібно отримати доступ
' uid: ідентифікатор користувача
' password: його пароль
Повний код класу [ArticlesDaoSqlServer] виглядає так:
Imports System
Imports System.Collections
Imports System.Data.SqlClient
Namespace istia.st.articles.dao
Public Class ArticlesDaoSqlServer
Implements istia.st.articles.dao.IArticlesDao
' приватні поля
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
' конструктор
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' сервер: ім'я екземпляра SQL, до якого потрібно підключитися
' databaseName: ім'я бази даних, до якої потрібно підключитися
' uid: ідентифікатор користувача
' password: його пароль
'отримуємо ім’я бази даних, передане як аргумент
Me.databaseName = databaseName
'— створюється екземпляр з'єднання
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' готуємо запити 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
' ексклюзивний блок
SyncLock Me
' готуємо запит на вставку
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
'— виконується
Return executeUpdate(insertCommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний сегмент
SyncLock Me
' підготовка запиту на оновлення запасів
With updateStockCommand.Parameters
.Clear()
.Add(New SqlParameter("@mvt", mouvement))
.Add(New SqlParameter("@id", idArticle))
End With
'виконується
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний сегмент
SyncLock Me
Try
'виконується запит на вставку
executeUpdate(deleteAllCommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивна секція
SyncLock Me
Try
'виконується запит на вибірку
Dim articles As IList = executeQuery(selectAllCommand)
'повертається список
Return articles
Catch ex As Exception
'помилка запиту
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
' ексклюзивна секція
SyncLock Me
' готується запит SELECT
With selectSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'— виконується
Try
'виконується запит
Dim articles As IList = executeQuery(selectSomeCommand)
'перевіряється, чи знайдено статтю
If articles.Count = 0 Then Return Nothing
'повертаємо товар
Return CType(articles.Item(0), Article)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний блок
SyncLock Me
' готується запит на оновлення
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
' виконується
Try
'виконується запит на вставку
Return executeUpdate(updatecommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний сегмент
SyncLock Me
' готуємо запит на видалення
With deleteSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'виконується
Try
'виконується запит на видалення
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'помилка запиту
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
' виконання запиту SELECT
' оголошення об’єкта, що надає доступ до всіх рядків таблиці результатів
Dim myReader As SqlDataReader = Nothing
Try
'створюється з'єднання з BDD
connexion.Open()
'— виконання запиту
myReader = query.ExecuteReader()
'оголошується список записів для подальшого повернення
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'готується товар із значеннями з зчитувача
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)
'додається товар до списку
articles.Add(unArticle)
End While
'— повертається результат
Return articles
Finally
' звільнення ресурсів
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
' виконання запиту на оновлення
Try
'— встановлення з'єднання з BDD
connexion.Open()
'виконується запит
Return updateCommand.ExecuteNonQuery()
Finally
' звільнення ресурсів
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Читачеві пропонується ознайомитися з цим кодом у контексті коментарів до класу [ArticlesDaoPlainODBC], наведених раніше.
2.4.2. Створення збірки шару [dao]
Новий проєкт Visual Studio має таку структуру:

Проєкт налаштовано на генерацію DLL під назвою [webarticles-dao.dll]:
![]() | ![]() |
2.4.3. Тести Nunit для шару [dao]
2.4.3.1. Створення сервера даних SQL
Щоб протестувати наш новий шар [dao], нам потрібне джерело даних SQL Server, а отже, і SGBD SQL Server. Фактично ми будемо використовувати SGBD MSDE (MicroSoft Data Engine) (параграф 3.12), який є версією сервера SQL, обмеженою лише кількістю одночасних користувачів. За допомогою [EMS MS SQL Manager] (параграф 3.14) ми створюємо таку базу статей в екземплярі MSDE під назвою [portable1_tahe\msde140405]:
![]() | ![]() |

База належить користувачеві [mdparticles] з паролем [admarticles]. Команда Transact-SQL для створення таблиці [ARTICLES] виглядає так:
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
Ми створюємо кілька статей:

2.4.3.2. Тестовий клас NUnit
Клас тесту Nunit для класу реалізації [ArticlesDaoSqlServer] є таким самим, як і для класу [ArticlesDaoPlainODBC] (див. розділ 2.3.3.2). Ми дотримуємося аналогічного підходу для підготовки тесту Nunit для цього класу:
- у папці Visual Studio проекту [dao-sqlserver] створюємо папку [tests] (праворуч), скопіювавши папку [tests] з проекту [dao-odbc] (ліворуч):
![]() | ![]() |
- у папці [tests] проекту [dao-sqlserver], замінюємо DLL [webarticles-dao.dll] на DLL [webarticles-dao.dll], отримані в результаті генерації проекту [dao-sqlserver]
- ми змінюємо файл конфігурації [spring-config.xml], щоб створити екземпляр нового класу [ArticlesDaoSqlServer]:
Коментарі:
- у рядку 7 об’єкт [articlesdao] тепер пов’язаний з екземпляром класу [ ArticlesDaoSqlServeur]
- цей клас має конструктор із чотирма аргументами:
- ім'я екземпляра MSDE, що використовується — рядок 9
- ім’я бази даних — рядок 12
- ідентифікатор, за допомогою якого буде здійснюватися доступ до бази даних — рядок 15
- пароль, пов'язаний із цим ідентифікатором — рядок 18
Тут ми використовуємо дані з джерела MSDE, яке ми створили раніше.
2.4.3.3. Тестування
Ми готові до тестування. За допомогою програми [Nunit-Gui] завантажуємо DLL [test-webarticles-dao.dll] із папки [tests], зазначеної вище, і виконуємо тест [testGetAllArticles]:

Незважаючи на те, що тестовому класу спочатку було надано ім’я [NUnitTestArticlesDaoArrayList], яке було збережено, оскільки ми використовуємо DLL та [tests-webarticles-dao.dll], отримані з цього класу, саме клас [ArticlesDaoSqlserver] тут і тестується. Знімок екрана показує, що ми правильно витягли статті, які розмістили в таблиці [ARTICLES]. Тепер проведемо повний набір тестів:

У лівому вікні ми бачимо список протестованих методів. Колір крапки перед назвою кожного методу вказує на успішне виконання (зелений) або невдачу (червоний) методу. Читач, який переглядає цей документ на екрані, зможе побачити, що всі тести пройшли успішно.
2.4.4. Інтеграція нового шару [dao] у додаток [webarticles]
Ми дотримуємося підходу, описаного в розділі 2.3.4. Вносимо такі зміни до вмісту папки [runtime]:
- у папці [bin] файл DLL ізстарого шару [dao] замінюється на DLL з нового шару [dao], реалізованого класом [ArticlesDaoSqlServer]
- у [runtime] файл конфігурації [web.config] замінюється файлом, який враховує новий клас реалізації:
Коментарі:
- у рядках 15–33 до синглтона [articlesDao] прив’язується екземпляр нового класу [ArticlesDaoSqlServer]. Це єдина зміна. Ми вже стикалися з нею під час тестування нового шару [dao]
Ми готові до тестування. Ми зберігаємо ту саму конфігурацію веб-сервера [Cassini], що й раніше. Ми ініціалізуємо таблицю статей [MSDE] із такими значеннями:

Переконайтеся, що веб-сервер Cassini, а також SGBD та MSDE (у даному випадку екземпляр portable1_tahe\msde140405) запущені. За допомогою браузера зверніться до URL та [http://localhost/webarticles/main.aspx]:

![]() |
Тепер перевіримо вміст таблиці [ARTICLES] у базі даних [MSDE]:

Товари [ballon foot] та [raquette tennis] було придбано, а їхні запаси зменшено на кількість, що була придбана. Товар [rollers] не вдалося придбати, оскільки запитувана кількість перевищувала кількість на складі. Пропонуємо читачеві провести додаткові тести.
2.4.5. Клас реалізації [ArticlesDaoOleDb]
2.4.5.1. , джерела даних OleDb
Третя реалізація шару [dao] передбачає, що дані знаходяться в базі даних, доступ до якої здійснюється через драйвер OleDb. Принцип роботи джерел OleDb аналогічний принципу роботи джерел ODBC. Програма, що використовує джерело OleDb, робить це через стандартний інтерфейс, спільний для всіх джерел OleDb. Зміна джерела OleDb зводиться до зміни драйвера OleDb. Сам код при цьому не змінюється.
За допомогою Visual Studio можна дізнатися, які драйвери OleDb доступні на вашому комп’ютері:
- відкрийте оглядач серверів за допомогою [Affichage/Explorateur de serveurs]:

- щоб додати нове з’єднання, клацніть правою кнопкою миші на [Connexion de données] і виберіть опцію [Ajouter une connexion]. Після цього з’явиться майстер, за допомогою якого можна налаштувати параметри з’єднання:

- панель [Fournisseur] містить список доступних драйверів OLEDB. Для нового шару [dao] ми використаємо драйвер [Microsoft Jet 4.0 OLE DB Provider], який надає доступ до баз даних ACCESS.
- На деякий час вийдемо з Visual Studio, щоб створити базу даних ACCESS [articles.mdb], яка містить лише одну таблицю:

- Структура таблиці така:
числовий — ціле — первинний ключ | |
текст — 20 символів — | |
числовий — дійсне число подвійної точності | |
числовий — ціле | |
числовий — ціле число |
- Повернімося до Visual Studio і створимо нове з’єднання, як описано раніше:

- вибираємо драйвер [Microsoft Jet 4.0] і переходимо до панелі [Connexion]:

- за допомогою кнопки [1] вкажіть щойно створену базу даних ACCESS, а потім завершіть налаштування з’єднання за допомогою кнопки [Terminer]. Створене з’єднання тепер відображається у списку доступних з’єднань:

- подвійне клацання на таблиці [ARTICLES] надає доступ до її вмісту:

- після чого можна додавати, редагувати та видаляти рядки в таблиці.
- Виберіть у провіднику серверів нове з’єднання, щоб відкрити його вікно властивостей:

- Корисно знати рядок підключення. Він знадобиться нам для підключення до бази даних:
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
- З цього рядка ми запам’ятаємо лише такі елементи:
2.4.5.2. Код класу [ArticlesDaoOleDb]
Клас [ArticlesDaoOleDb] дуже схожий на клас [ArticlesDaoPlainODBC], який ми розглядали раніше. Тому ми вкажемо лише зміни, внесені порівняно з попередньою версією:
- необхідні класи знаходяться в просторі імен [System.Data.OleDb] замість простору імен [System.Data.Odbc]
- з’єднання типу [OdbcConnection] тепер має тип [OleDbConnection]
- об’єкти [OdbcCommand] тепер мають тип [OleDbCommand]
Конструктор класу приймає як єдиний параметр рядок підключення до бази даних:
' конструктор
Public Sub New(ByVal connectString As String)
' connectString: рядок підключення до джерела OleDb
'створюється екземпляр з'єднання
connexion = New OleDbConnection(connectString)
' готуються запити SQL
...
End Sub
Повний код класу [ArticlesDaoOleDb] такий:
Imports System
Imports System.Collections
Imports System.Data.OleDb
Namespace istia.st.articles.dao
Public Class ArticlesDaoOleDb
Implements istia.st.articles.dao.IArticlesDao
' приватні поля
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
' конструктор
Public Sub New(ByVal connectString As String)
' connectString: рядок підключення до джерела OleDb
'— створюється екземпляр з'єднання
connexion = New OleDbConnection(connectString)
' готуються запити 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
' ексклюзивний блок
SyncLock Me
' готується запит на вставку
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
'— виконується
Return executeUpdate(insertCommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний сегмент
SyncLock Me
' підготовка запиту на оновлення запасів
With updateStockCommand.Parameters
.Clear()
.Add(New OleDbParameter("mvt1", mouvement))
.Add(New OleDbParameter("id", idArticle))
.Add(New OleDbParameter("mvt2", mouvement))
End With
'виконується
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний сегмент
SyncLock Me
Try
'виконується запит на вставку
executeUpdate(deleteAllCommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивна секція
SyncLock Me
Try
'виконується запит на вибірку
Dim articles As IList = executeQuery(selectAllCommand)
'повертається список
Return articles
Catch ex As Exception
'помилка запиту
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
' ексклюзивна секція
SyncLock Me
' готується запит select
With selectSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'— виконується
Try
'— виконується запит
Dim articles As IList = executeQuery(selectSomeCommand)
'перевіряється, чи знайдено статтю
If articles.Count = 0 Then Return Nothing
'повертається стаття
Return CType(articles.Item(0), Article)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний блок
SyncLock Me
' готується запит на оновлення
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
' виконується
Try
'виконується запит на вставку
Return executeUpdate(updatecommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний сегмент
SyncLock Me
' готуємо запит на видалення
With deleteSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'виконується
Try
'виконується запит на видалення
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'помилка запиту
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
' виконання запиту SELECT
' оголошення об’єкта, що надає доступ до всіх рядків таблиці результатів
Dim myReader As OleDbDataReader = Nothing
Try
'створюється з'єднання з BDD
connexion.Open()
'— виконання запиту
myReader = query.ExecuteReader()
'оголошується список записів для подальшого повернення
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'готуємо товар із значеннями з зчитувача
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)
'додаємо товар до списку
articles.Add(unArticle)
End While
'— повертається результат
Return articles
Finally
' звільнення ресурсів
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
' виконання запиту на оновлення
Try
'— встановлення з'єднання з BDD
connexion.Open()
'виконується запит
Return sqlCommand.ExecuteNonQuery()
Finally
' звільнення ресурсів
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Читачеві пропонується ознайомитися з цим кодом у контексті коментарів до класу [ArticlesDaoPlainODBC], наведених раніше.
2.4.5.3. Створення збірки шару [dao]
Новий проєкт Visual Studio має таку структуру:

Проєкт налаштовано на генерацію DLL під назвою [webarticles-dao.dll]:
![]() | ![]() |
2.4.5.4. Тести Nunit для шару [dao]
2.4.5.4.1. Тестовий клас NUnit
Клас тесту Nunit для класу реалізації [ArticlesDaoOleDb] є таким самим, як і для класу [ArticlesDaoPlainODBC] (див. розділ 2.3.3.2). Ми дотримуємося аналогічного підходу для підготовки тесту Nunit для цього класу:
- у папці Visual Studio проекту [dao-oledb] створюємо папку [tests] (праворуч), скопіювавши папку [tests] з проекту [dao-odbc] (ліворуч):
![]() | ![]() |
- у папці [tests] проекту [dao-oledb] замінюємо DLL [webarticles-dao.dll] на DLL [webarticles-dao.dll], отриманий у результаті генерації проекту [dao-oledb]
- ми змінюємо файл конфігурації [spring-config.xml], щоб створити екземпляр нового класу [ArticlesDaoOleDb]:
Коментарі:
- у рядку 7 об’єкт [articlesdao] тепер пов’язаний з екземпляром класу [ ArticlesDaoOleDb]
- цей клас має конструктор з одним аргументом: рядок підключення до бази даних OleDb ACCESS — рядок 9
2.4.5.4.2. Tests
Ми готові до тестування. За допомогою додатка [Nunit-Gui] завантажуємо DLL [test-webarticles-dao.dll] із вищезгаданої папки [tests] і виконуємо тест [testGetAllArticles]:

Незважаючи на те, що спочатку класу тесту було присвоєно ім’я [NUnitTestArticlesDaoArrayList], насправді тут тестується саме клас [ArticlesDaoOleDb]. Знімок екрана показує, що ми правильно отримали статті, які ми розмістили в таблиці [ARTICLES]. Тепер виконаємо всі тести:

Читач, який переглядає цей документ на екрані, побачить, що всі тести пройшли успішно (зелений колір).
2.4.5.5. Інтеграція нового шару [dao] у додаток [webarticles]
Ми дотримуємося процедури, описаної в розділі 2.3.4. Вносимо такі зміни до вмісту папки [runtime]:
- у файлі [bin] файл DLL ізстарого шару [dao] замінюється на DLL з нового шару [dao], реалізованого класом [ArticlesDaoOleDb]
- у [runtime] файл конфігурації [web.config] замінюється файлом, який враховує новий клас реалізації:
Коментарі:
- у рядках 14–18 до синглтона [articlesDao] прив’язується екземпляр нового класу [ArticlesDaoOleDb]. Це єдина зміна.
Ми зберігаємо ту саму конфігурацію веб-сервера [Cassini], що й раніше. Ми ініціалізуємо таблицю статей із такими значеннями:

Переконайтеся, що база статей не використовується програмою, такою як Visual Studio або ACCESS. За допомогою браузера ми запитуємо URL [http://localhost/webarticles/main.aspx]:

![]() |
Тепер перевіримо вміст таблиці [ARTICLES] та ACCESS:

Артикули [pantalon] та [jupe] було придбано, а їхні запаси зменшено на кількість, що була придбана. Артикул [manteau] не вдалося придбати, оскільки запитувана кількість перевищувала кількість на складі. Пропонуємо читачеві провести додаткові тести.
2.5. Клас реалізації [ArticlesDaoFirebirdProvider]
2.5.1. Провайдер доступу Firebird-net-provider
Ми вже використовували джерело даних [Firebird], до якого зверталися через драйвер ODBC. Хоча драйвери ODBC забезпечують високу можливість повторного використання коду, що їх використовує, вони, однак, менш ефективні, ніж драйвери, написані спеціально для цільового SGBD. SGBD [Firebird] можна використовувати за допомогою бібліотеки спеціальних класів, яку можна завантажити з веб-сайту Firebird [http://firebird.sourceforge.net/]. На сторінці завантажень наведено такі посилання (квітень 2005 року):
![]()
Посилання [firebird-net-provider] слід використовувати для завантаження класів .NET, що забезпечують доступ до SGBD Firebird. В результаті встановлення пакета створюється папка, схожа на таку:

Нас цікавлять два елементи:
- [FirebirdSql.Data.Firebird.dll]: збірка, що містить класи .NET для доступу до SGBD Firebird
- [FirebirdNETProviderSDK.chm]: документація щодо цих класів
Далі, щоб проект Visual Studio міг використовувати ці класи, ми зробимо дві речі:
- помістимо збірку [FirebirdSql.Data.Firebird.dll] у папку [bin] проекту
- додамо цей самий асемблі до посилань проекту
2.5.2. Код класу [ArticlesDaoFirebirdProvider]
Клас [ArticlesDaoFirebirdProvider] дуже схожий на клас [ArticlesDaoSqlServer], який ми розглядали раніше. Тому ми вкажемо лише зміни, внесені порівняно з цією версією:
- необхідні класи знаходяться в просторі імен [FirebirdSql.Data.Firebird] замість простору імен [System.Data.SqlClient]
- з’єднання типу [SqlConnection] тепер має тип [FbConnection]
- об’єкти [SqlCommand] тепер мають тип [FbCommand]
- об’єкти [SqlParameter] тепер мають тип [FbParameter]
Конструктор класу приймає чотири параметри, за допомогою яких він формує рядок підключення до бази даних:
' конструктор
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' сервер: ім’я хост-машини SGBD
' databaseName: шлях до бази даних
' uid: ідентифікатор користувача, що входить у систему
' password: його пароль
...
End Sub
Повний код класу [ArticlesDaoFirebirdProvider] такий:
Imports System
Imports System.Collections
Imports FirebirdSql.Data.Firebird
Namespace istia.st.articles.dao
Public Class ArticlesDaoFirebirdProvider
Implements istia.st.articles.dao.IArticlesDao
' приватні поля
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
' виробник
Public Sub New(ByVal serveur As String, ByVal databasePath As String, ByVal uid As String, ByVal password As String)
' сервер: ім’я хост-машини SGBD Firebird
' databaseName: шлях до бази даних, з якою потрібно працювати
' uid: ідентифікатор користувача, який підключається до бази даних
' password: його пароль
'отримуємо ім’я бази даних, передане як аргумент
Me.databasePath = databasePath
'— створюється екземпляр з'єднання
Dim connectString As String = String.Format("DataSource={0};Database={1};User={2};Password={3}", serveur, databasePath, uid, password)
connexion = New FbConnection(connectString)
' готуємо запити 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
' ексклюзивний блок
SyncLock Me
' готуємо запит на вставку
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
'— виконується
Return executeUpdate(insertCommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний сегмент
SyncLock Me
' підготовка запиту на оновлення запасів
With updateStockCommand.Parameters
.Clear()
.Add(New FbParameter("@mvt", mouvement))
.Add(New FbParameter("@id", idArticle))
End With
'виконується
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний сегмент
SyncLock Me
Try
'виконується запит на вставку
executeUpdate(deleteAllCommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивна секція
SyncLock Me
Try
'виконується запит на вибірку
Dim articles As IList = executeQuery(selectAllCommand)
'повертається список
Return articles
Catch ex As Exception
'помилка запиту
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
' ексклюзивна секція
SyncLock Me
' готується запит select
With selectSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'— виконується
Try
'виконується запит
Dim articles As IList = executeQuery(selectSomeCommand)
'перевіряється, чи знайдено статтю
If articles.Count = 0 Then Return Nothing
'повертаємо товар
Return CType(articles.Item(0), Article)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний блок
SyncLock Me
' готується запит на оновлення
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
' виконується
Try
'виконується запит на вставку
Return executeUpdate(updatecommand)
Catch ex As Exception
'помилка запиту
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
' ексклюзивний сегмент
SyncLock Me
' готуємо запит на видалення
With deleteSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'виконується
Try
'виконується запит на видалення
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'помилка запиту
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
' виконання запиту SELECT
' оголошення об’єкта, що надає доступ до всіх рядків таблиці результатів
Dim myReader As FbDataReader = Nothing
Try
'створюється з'єднання з BDD
connexion.Open()
'— виконання запиту
myReader = query.ExecuteReader()
'оголошується список записів для подальшого повернення
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'готується товар із значеннями з зчитувача
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)
'додаємо товар до списку
articles.Add(unArticle)
End While
'— повертається результат
Return articles
Finally
' звільнення ресурсів
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
' виконання запиту на оновлення
Try
'— встановлюється з'єднання з BDD
connexion.Open()
'виконується запит
Return updateCommand.ExecuteNonQuery()
Finally
' звільнення ресурсів
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Читачеві пропонується ознайомитися з цим кодом у світлі коментарів до класу [ArticlesDaoSqlServer], наведених раніше.
2.5.3. Створення збірки шару [dao]
Новий проєкт Visual Studio має таку структуру:

Зверніть увагу на наявність збірки [FirebirdSql.Data.Firebird.dll] у посиланнях проекту. Ця збірка DLL була розміщена в папці [bin] проекту. Проєкт налаштовано на генерацію збірки DLL під назвою [webarticles-dao.dll]:
![]() | ![]() |
2.5.4. Тести Nunit для шару [dao]
2.5.4.1. Клас тесту NUnit
Тестовий клас Nunit для класу реалізації [ArticlesDaoFirebirdProvider] є таким самим, як і для класу [ArticlesDaoPlainODBC] (див. розділ 2.3.3.2). Ми дотримуємося аналогічного підходу для підготовки тесту Nunit для класу [ArticlesDaoFirebirdProvider]:
- у папці Visual Studio проекту [dao-firebird-provider] створюємо папку [tests] (праворуч), скопіювавши папку [bin] з тестового проекту шару [dao-odbc] (ліворуч):
![]() | ![]() |
- у папці [tests] замінюємо DLL та [webarticles-dao.dll] на DLL та [webarticles-dao.dll], отримані в результаті генерації проекту [dao-firebird-provider]
- ми змінюємо файл конфігурації [spring-config.xml], щоб створити екземпляр нового класу [ArticlesDaoFirebirdProvider]:
Коментарі:
- у рядку 7 об’єкт [articlesdao] тепер пов’язаний з екземпляром класу [ArticlesDaoFirebirdProvider]
- цей клас має конструктор із чотирма аргументами
- хост-машина для SGBD — рядок 9
- шлях до бази даних Firebird — рядок 12
- логін користувача, що входить у систему — рядок 15
- його пароль — рядок 18
2.5.4.2. Тестування
Таблиця [ARTICLES] у джерелі даних заповнена такими записами (використовуйте IBExpert):

Ми готові до тестування. За допомогою додатка [Nunit-Gui] завантажуємо DLL та [test-webarticles-dao.dll] із вищезазначеної папки [tests] і виконуємо тест [testGetAllArticles]:

Незважаючи на те, що спочатку класу тесту було присвоєно ім’я [NUnitTestArticlesDaoArrayList], насправді тут тестується саме клас [ArticlesDaoFirebirdProvider]. Знімок екрана показує, що ми правильно отримали статті, які ми розмістили в таблиці [ARTICLES]. Тепер виконаємо всі тести:

Читач, який переглядає цей документ на екрані, побачить, що всі тести пройшли успішно (зелений колір). Що він не може побачити, так це те, що тести пройшли значно швидше, ніж із базою статей, до якої здійснювався доступ через драйвер ODBC з нашої першої реалізації.
2.5.5. Інтеграція нового шару [dao] у додаток [webarticles]
Ми дотримуємося підходу, який вже двічі пояснювали, зокрема в розділі 2.3.4. Вносимо такі зміни до вмісту папки [runtime]:
- у папці [bin] файл DLL ізстарого шару [dao] замінюється на DLL з нового шару [dao], реалізованого класом [ArticlesDaoFirebirdProvider]. Ми також розміщуємо там DLL, необхідний для Firebird [FirebirdSql.Data.Firebird.dll]:

- у [runtime] файл конфігурації [web.config] замінюється файлом, який враховує новий клас реалізації:
Коментарі:
- у рядках 14–27 до синглтона [articlesDao] прив’язується екземпляр нового класу [ArticlesDaoFirebirdProvider]. Це єдина зміна.
Ми готові до тестування. Налаштовуємо веб-сервер [Cassini] так само, як у попередніх тестах. Ініціалізуємо таблицю статей із такими значеннями:

За допомогою браузера ми звертаємося до URL [http://localhost/webarticles/main.aspx]:

![]() |
Тепер перевіримо вміст таблиці [ARTICLES]:

Артикули [crayon bille] та [ramette 50 feuilles] було придбано, а їхні запаси зменшено на кількість, що була придбана. Артикул [stylo plume] не вдалося придбати, оскільки запитувана кількість перевищувала кількість на складі. Пропонуємо читачеві провести додаткові тести.
2.5.6. Клас реалізації [ArticlesDaoSqlMap]
2.5.6.1. Продукт Ibatis SqlMap
Ми написали чотири різні реалізації шару [dao] для нашого додатка [webarticles]. У кожному випадку нам вдалося інтегрувати новий рівень [dao] у додаток [webarticles] без перекомпіляції двох інших рівнів — [web] та [domain]. Нагадаємо, що цього вдалося досягти завдяки двом архітектурним рішенням:
- доступ до шарів через інтерфейси
- інтеграція шарів за допомогою Spring
Ми хочемо піти трохи далі. Хоча наші чотири реалізації шару [dao] є різними, вони мають разючі подібності. Після написання першої реалізації три інші були отримані практично шляхом копіювання-вставлення та заміни деяких ключових слів іншими. Сама логіка при цьому не змінювалася. Виникає питання: чи не можна було б створити таку реалізацію, яка звільнила б нас від необхідності використовувати різні способи доступу до даних? Ми використовували чотири з них:
- доступ через драйвер ODBC до джерела даних ODBC
- прямий доступ до бази даних SQL Server
- доступ через драйвер Ole Db до джерела даних Ole Db
- прямий доступ до бази даних Firebird
Інструмент Ibatis SqlMap [[http://www.ibatis.com/] дозволяє розробляти рівні доступу до даних, незалежні від фактичного типу джерела даних. Доступ до даних забезпечується за допомогою:
- конфігураційних файлів, у яких міститься інформація, що визначає джерело даних та операції, які потрібно виконати над ним
- бібліотеки класів, які використовують цю інформацію для доступу до даних
Інструмент Ibatis SqlMap спочатку був розроблений для платформи Java. Його портування на платформу .NET відбулося нещодавно і, судячи з усього, містить деякі помилки (особиста думка, яка потребує ретельної перевірки). Проте, оскільки інструмент добре зарекомендував себе на платформі Java, представляється доцільним ознайомити вас із його версією для платформи .NET.
2.5.6.2. Де знайти IBATIS SqlMap?
Головний сайт Firebird — [http://www.ibatis.com/]. На сторінці завантажень наведено такі посилання:

Виберемо посилання [Stable Binaries], яке перенаправляє нас на сторінку [SourceForge.net]. Пройдіть процес завантаження до кінця. У результаті отримаємо архів у форматі zip, що містить такі файли:

У проєкті Visual Studio, що використовує Ibatis SqlMap, потрібно виконати дві дії:
- помістити вищезазначені файли в папку [bin] проекту
- додати до проекту посилання на кожен із цих файлів
2.5.6.3. Конфігураційні файли Ibatis SqlMap
Джерело даних [SqlMap] буде визначено за допомогою таких файлів конфігурації:
- providers.config: визначає бібліотеки класів, які слід використовувати для доступу до даних
- sqlmap.config: визначає параметри з’єднання, яке потрібно встановити
- файли маппінгу: визначають операції, які потрібно виконати над даними
Логіка цих файлів така:
- для доступу до даних нам знадобиться з’єднання. Для його представлення ми вже зустрічали кілька класів: OdbcConnection, SqlConnection, OleDbConnection, FbConnection. Нам також знадобиться об’єкт [Command] для відправлення запитів SQL: OdbcCommand, SqlCommand, OleDbCommand, FbCommand. І т. д. У файлі [providers.config] ми визначаємо набір класів, які нам потрібні.
- Файл [sqlmap.config], по суті, визначає рядок підключення до бази даних, яка містить дані. З'єднання з базою даних буде відкрито шляхом інстанціювання класу [Connection], визначеного у файлі [providers.config], у конструктор якого буде передано рядок з'єднання, визначений у файлі [sqlmap.config].
- Файли маппінгу визначають:
- відповідності між рядками таблиць даних та класом .NET, екземпляри якого міститимуть ці рядки
- операції SQL, які потрібно виконати. Вони ідентифікуються за іменем. Код .NET виконує ці операції за їхніми іменами, що призводить до видалення всього коду SQL з коду .NET.
2.5.6.4. Конфігураційні файли проєкту [dao-sqlmap]
Розглянемо на прикладі точну природу файлів конфігурації SqlMap. Розглянемо випадок, коли джерелом даних є джерело ODBC Firebird з розділу 2.3.3.1.
2.5.6.4.1. providers.config
Файл [providers.config] для джерела ODBC виглядає так:
Коментарі:
- файл [providers.config] поширюється разом із пакетом [SqlMap]. Він пропонує кілька стандартних провайдерів (provider). Наведений вище код взято безпосередньо з цього файлу.
- <provider> має ім’я — рядок 6 — яке може бути будь-яким
- <provider> може бути активований ([enabled=true]) або неактивований ([enabled=false]). Якщо він активований, то DLL, на який є посилання у рядку 8, має бути доступним. Файл [providers.config] може містити кілька тегів <provider>.
- рядок 8 — ім’я збірки, що містить класи, визначені в рядках 9–15
- рядок 9 — клас, який використовується для створення з’єднання
- рядок 10 — клас, який слід використовувати для створення об’єкта [Command] для відправлення команд SQL
- рядок 11 — клас, який використовується для управління параметрами налаштованої команди SQL
- рядок 12 — клас переліку можливих типів даних для полів таблиці
- рядок 13 — ім’я властивості об’єкта [Parameter], що містить тип значення цього параметра
- рядок 14 — ім’я класу [Adapter], що дозволяє створювати об’єкти [DataSet] на основі джерела даних
- рядок 15 — ім'я класу [CommandBuilder], який у поєднанні з об'єктом [Adapter] дозволяє автоматично генерувати його властивості [InsertCommand, DeleteCommand, UpdateCommand] на основі його властивості [SelectCommand]
- рядки 16–19 — тут визначається, як обробляються налаштовані команди SQL. Залежно від випадку, потрібно вказати, наприклад:
або
У першому випадку йдеться про формальні позиційні параметри. Фактичні значення цих параметрів слід вказувати у порядку формальних параметрів. У другому випадку маємо справу з іменованими параметрами. Значення такого параметра вказується шляхом зазначення його імені. Порядок більше не має значення.
- рядок 16 — вказується, що джерела ODBC використовують позиційні параметри
- рядки 17–19 — стосуються іменованих параметрів. Тут їх немає.
Ця інформація дозволяє SqlMap, наприклад, дізнатися, який клас він повинен інстанціювати для створення з’єднання. У даному випадку це буде клас [OdbcConnection] (рядок 9).
2.5.6.4.2. sqlmap.config
Файл [providers.config] визначає класи, які слід використовувати для доступу до джерела ODBC. Він не вказує жодного джерела ODBC. Це робить файл [sqlmap.config]:
Коментарі:
- рядок 3 — визначається файл властивостей [properties.xml]. У ньому визначаються пари (ключ, значення). Ключі можуть бути будь-якими. Значення, пов’язане з ключем C, отримується за допомогою нотації ${C} у файлі [sqlmap.config]. Ось файл [properties.xml], який буде пов’язаний із попереднім файлом [sqlmap.config]:
рядок 3 — визначено ключ [provider]. Його значенням є ім’я тегу <provider>, яке слід використовувати у файлі [providers.config]
рядок 4 — визначено ключ [connectionString]. Його значенням є рядок підключення, який слід використовувати для встановлення з’єднання з джерелом даних ODBC Firebird.
- рядки 4–7 — параметри конфігурації:
- рядок 5 — запити SQL ідентифікуватимуться за іменем, яке саме по собі може бути частиною простору імен. [useStatementNamespaces="false"] вказує, що простори імен не використовуватимуться.
- рядок 6 — SqlMap має різні стратегії кешування для мінімізації звернень до джерела даних. [cacheModelsEnabled="false"] вказує, що жодна з них не використовуватиметься.
- рядки 9–13 — визначаються характеристики джерела даних:
- рядок 10 — ім’я <provider> з [providers.config], яке слід використовувати
- рядок 11 — рядок підключення до джерела даних
- рядок 12 — менеджер транзакцій. Тут ми його не використовували, але все ж залишили цей рядок, оскільки він був у стандартному дистрибутивному файлі.
- рядки 14–16 — список файлів, що визначають операції SQL, які потрібно виконати над джерелом даних.
- рядок 15 — визначає файл маппінгу [articles.xml]
2.5.6.4.3. articles.xml
Цей файл виконує дві функції:
- визначення об’єктного маппінгу таблиць джерела даних. У найпростіших випадках це зводиться до прив’язки класу до рядка таблиці.
- визначення параметризованих операцій SQL та їх іменування.
Ми будемо використовувати такий файл: [articles.xml]:
Коментарі:
- рядки 4–11 — визначається відповідність між рядком таблиці [ARTICLES] джерела даних та класом [istia.st.articles.dao.Article]. Кожному стовпцю (column) таблиці відповідає властивість (property) класу [Article]. Це зіставлення дозволяє [SqlMap] сформувати результат операції SQL SELECT. Кожен рядок результату операції SELECT буде розміщений в об’єкті [Article] відповідно до правил зіставлення.
- рядок 5 — маппінг позначається тегом <resultMap> і отримує ім’я за допомогою атрибута [id="article"]. Відповідний клас позначається атрибутом [class="istia.st.articles.dao.Article"].
- рядки 14–44 — визначаються необхідні операції SQL
- рядки 16–18 — визначається операція SELECT, яка називається [getAllArticles]
- рядок 16 — операція SELECT називається [name= "getAllArticles "], а маппінг, який слід використовувати, визначається атрибутом [resultMap="article"]. Отже, тут робиться посилання на маппінг із рядків 5–11
- рядок 17 — текст команди SQL, яку потрібно виконати
- рядки 20–22 — визначається команда SQL-Delete [clearAllArticles], призначена для очищення таблиці товарів.
- рядки 24–27 — визначається команда SQL-Insert [insertArticle], призначена для додавання нового товару до таблиці товарів. Це запит, параметри якого задаються елементами (#id#, #nom#, #prix#, #stockactuel#, #stockminimum#). Значення цих п’яти елементів будуть отримані з об’єкта [Article], переданого як параметр: [parameterClass="istia.st.articles.dao.Article"]. Об’єкт-параметр повинен мати властивості (id, nom, prix, stockactuel, stockminimum), на які посилається налаштована команда SQL.
- рядки 29–31 — визначається команда SQL Delete [deleteArticle], призначена для видалення товару, номер якого відомий як #value#. Цей номер буде передано як параметр: [parameterClass="int"]. Це загальне правило. Коли параметр є єдиним, на нього посилаються за допомогою ключового слова #value# у тексті команди SQL.
- рядки 33–35 — визначається команда SQL-Update [modifyArticle], призначена для зміни товару, номер якого відомий. Як і у випадку з командою [insertArticle], п’ять необхідних даних будуть отримані з властивостей об’єкта [istia.st.articles.dao.Article].
- рядки 37–39 — визначається команда SQL-Select [getArticleById], яка дозволяє отримати запис товару, номер якого відомий.
- рядки 41–43 — визначається команда SQL-Update [changerStockArticle], яка змінює поле [stockactuel] товару, номер якого відомий. Дві необхідні інформації — номер #id# товару та приріст запасів #mouvement# — містяться у словнику: [parameterClass="Hashtable"]. Цей словник повинен мати два ключі: id та mouvement. Саме значення, пов’язані з цими двома ключами, будуть використовуватися в команді SQL.
2.5.6.4.4. Розташування файлів конфігурації
Розглянемо дві різні ситуації:
- у випадку тесту Nunit конфігураційні файли [SqlMap] будуть розміщені в тій самій папці, що й тестовані бінарні файли.
- у випадку веб-додатку вони будуть розміщені в кореневій папці додатка.
2.5.6.5. API від SqlMap
Класи SqlMap містяться у DLL, який зазвичай розміщують у папці [bin] додатка:

Додатки, що використовують класи з SqlMap, повинні імпортувати простір імен [IBatisNet.DataMapper]:
Усі операції SQL виконуються через синглтон типу [Mapper], клас із простору імен [IBatisNet.DataMapper ]. Синглтон отримується таким чином:
Щоб виконати команду SqlMap [getAllArticles], слід написати:
- метод [QueryForList] дозволяє отримати результат виконання команди SELECT у вигляді списку
- перший параметр — це назва команди SQL, яку потрібно виконати (див. articles.xml)
- другий параметр — це параметр, який потрібно передати у запиті SQL. Він повинен відповідати атрибуту [parameterClass] команди SqlMap. У [articles.xml] міститься [parameterClass=Nothing]. Тому тут передається нульовий покажчик.
- результат має тип IList. Об’єкти цього списку вказані атрибутом [resultMap] команди SQL-select: [resultMap="article"]. «article» — це ім’я маппінгу:
Клас, пов’язаний із цим маппінгом, — [istia.st.articles.dao.Article]. У підсумку змінна [articles], визначена вище, є списком об’єктів [ istia.st.articles.dao.Article]. Таким чином, ми отримали всю таблицю [ARTICLES] за допомогою однієї інструкції. Якщо таблиця [ARTICLES] порожня, ми отримуємо об’єкт [IList] із 0 елементами.
Щоб виконати команду SqlMap [getArticleById], потрібно написати:
- метод [QueryForObject] дозволяє отримати результат виконання команди SELECT, яка повертає лише один рядок
- перший параметр — це назва команди SqlMap, яку потрібно виконати
- другий параметр — це параметр, який потрібно передати у запиті SQL. Він повинен відповідати атрибуту [parameterClass] команди SqlMap. У [articles.xml] міститься [parameterClass="int"]. Тому тут передається ціле число, що позначає номер шуканого товару.
- Результат має тип Object. Якщо SELECT не повернув жодного рядка, то в якості результату отримуємо нульовий покажчик (nothing).
Щоб виконати команду SqlMap [insertArticle], потрібно написати:
- метод [Insert] дозволяє виконувати команди SQL INSERT
- перший параметр — це назва команди SqlMap, яку потрібно виконати
- другий параметр — це параметр, який потрібно передати цій команді. Він повинен відповідати атрибуту [parameterClass] команди SqlMap. У [articles.xml] міститься [parameterClass="istia.st.articles.dao.Article"]. Тому тут передається об’єкт типу [istia.st.articles.dao.Article].
Щоб виконати команду SqlMap [deleteArticle], потрібно написати:
- метод [Delete] дозволяє виконувати команди SQL DELETE
- перший параметр — це назва команди SQL, яку потрібно виконати
- другий параметр — це параметр, який потрібно передати цій команді. Він повинен відповідати атрибуту [parameterClass] команди SqlMap. У [articles.xml] міститься [parameterClass="int"]. Тому тут передається номер позиції, яку потрібно видалити.
- Результатом методу [Delete] є кількість видалених рядків
Аналогічно, щоб виконати команду SqlMap [clearAllArticles], потрібно ввести:
Щоб виконати команду SqlMap [modifyArticle], потрібно ввести:
- метод [Update] дозволяє виконувати команди SQL UPDATE
- перший параметр — це назва команди SqlMap, яку потрібно виконати
- другий параметр — це параметр, який потрібно передати цій команді. Він повинен відповідати атрибуту [parameterClass] команди SqlMap. У [articles.xml] міститься [parameterClass="istia.st.articles.dao.Article"]. Тому тут передається об’єкт типу [istia.st.articles.dao.Article].
- Результатом методу [Update] є кількість змінених рядків.
Аналогічно, щоб виконати команду SqlMap [changerStockArticle], потрібно написати:
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' оновлення
dim nbLignes as Integer= mappeur.Update("changerStockArticle", paramètres)
- другий параметр відповідає атрибуту [parameterClass] команди SqlMap. У [articles.xml] міститься [parameterClass="Hashtable"]. Команда SQL, налаштована на [changerStockArticle], використовує параметри [id, mouvement]. Тому тут передається словник, що містить ці два ключі.
2.5.6.6. Код класу [ArticlesDaoSqlMap]
Після наведених вище пояснень ми тепер можемо написати новий клас реалізації [ArticlesDaoSqlMap] наступного вигляду:
Option Explicit On
Option Strict On
Imports System
Imports IBatisNet.DataMapper
Imports System.Collections
Namespace istia.st.articles.dao
Public Class ArticlesDaoSqlMap
Implements IArticlesDao
' приватні поля
Dim mappeur As SqlMapper = Mapper.Instance
' список усіх статей
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
' додавання статті
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
SyncLock Me
Try
' unArticle: стаття для додавання
' вставлення
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
' видалення статті
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
SyncLock Me
Try
' id: ідентифікатор статті, яку потрібно видалити
' видалення
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
' редагування статті
Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
SyncLock Me
Try
' оновлення
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
' пошук статті
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
SyncLock Me
Try
' id: ідентифікатор статті, яку шукають
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
' видалення всіх товарів
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
' зміна запасів товару
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
SyncLock Me
Try
' id: ідентифікатор товару, запаси якого змінюються
' рух: рух запасів
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' оновлення
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
Читачеві пропонується ознайомитися з цим кодом у світлі пояснень, наведених для API та SqlMap. Цікаво відзначити, що використання [SqlMap] значно зменшило обсяг коду, який потрібно написати.
2.5.6.7. Генерація асемблера шару [dao]
Новий проєкт Visual Studio має таку структуру:

Зверніть увагу на наявність «асемблі», необхідних для SqlMap, у посиланнях проекту. Ці DLL було розміщено в папці [bin] проекту. Проєкт налаштовано на генерацію DLL під назвою [webarticles-dao.dll]:
![]() | ![]() |
2.5.6.8. Тести Nunit для шару [dao]
2.5.6.8.1. Тестовий клас NUnit
Тестовий клас Nunit для класу реалізації [ArticlesDaoSqlMap] є таким самим, як і для класу [ArticlesDaoPlainODBC] (див. розділ 2.3.3.2). Ми дотримуємося аналогічного підходу для підготовки тесту Nunit для класу [ArticlesDaoSqlMap]:
- у папці Visual Studio проекту [dao-sqlmap] створюємо папку [test1] (праворуч), скопіювавши папку [tests] з проекту [dao-odbc] (ліворуч):
![]() | ![]() |
- у папці [tests] замінюємо DLL та [webarticles-dao.dll] на DLL та [webarticles-dao.dll], отримані в результаті генерації проекту [dao-sqlmap].
- ми додаємо необхідні DLL до SqlMap, а також розглянуті конфігураційні файли [providers.config, sqlmap.config, properties.xml, articles.xml].
- Ми змінюємо файл конфігурації [spring-config.xml], щоб створити екземпляр нового класу [ArticlesDaoSqlMap]:
Коментарі:
- у рядку 7 об’єкт [articlesdao] тепер пов’язаний з екземпляром класу [ArticlesDaoSqlMap]
- цей клас не має конструктора. Буде використано конструктор за замовчуванням.
2.5.6.8.2. Tests
Таблиця [ARTICLES] у джерелі даних Firebird заповнена такими елементами:

Ми готові до тестування. За допомогою програми [Nunit-Gui] завантажуємо DLL та [test-webarticles-dao.dll] із вищезгаданої папки [test1] і виконуємо тест [testGetAllArticles]:

Незважаючи на те, що спочатку класу тесту було присвоєно ім’я [NUnitTestArticlesDaoArrayList], насправді тут тестується саме клас [ArticlesDaoSqlMap]. Знімок екрана показує, що ми правильно отримали статті, які ми розмістили в таблиці [ARTICLES]. Тепер виконаємо всі тести:

Читач, який переглядає цей документ на екрані, побачить, що деякі тести пройшли успішно (зелений колір), а інші — ні (червоний колір). Тести, що завершилися невдало, — це [testArticleAbsent] та [testChangerStockArticle]. Після тривалих досліджень з’ясувалося, що причини цих невдач такі:
- у тесті [testArticleAbsent] вимагається змінити статтю, якої не існує. Для цього використовується метод [modifieArticle], який повертає кількість змінених рядків, тобто 0 або 1. У цьому випадку результат мав би дорівнювати 0. Натомість виникає виняток типу [IBatisNet.Common.Exceptions.ConcurrentException].
- У [changerStockArticle] знову відбувається операція типу [update]. Йдеться про зменшення запасу на кількість, що перевищує сам запас. Для цього використовується метод [changerStockArticle], який повертає кількість змінених рядків, тобто 0 або 1. Команда SQL була написана для того, щоб уникнути оновлення (див. команду SQL «changerStockArticle» у articles.xml), яке призвело б до виникнення від’ємного залишку. Тут очікується, що результатом застосування методу [changerStockArticle] буде 0. Знову виникає виняток типу [IBatisNet.Common.Exceptions.ConcurrentException].
Можливих джерел помилок багато:
- код класу [ArticlesDaoSqlMap] містить помилку. Це можливо. Однак він походить від порту класу Java, який працював коректно з версією SqlMap для Java.
- версія .NET класу SqlMap містить помилку
- драйвер ODBC для Firebird містить помилки
- ...
Оскільки немає впевненості, ми обійдемо цю перешкоду, перехопивши згадане виключення [IBatisNet.Common.Exceptions.ConcurrentException]. Новий код класу [ArticlesDaoSqlMap] виглядає так:
Зміни внесені у рядки: 28, 41, 69. Для операцій SQL типу [UPDATE, DELETE], якщо виникає виняток типу [IBatisNet.Common.Exceptions.ConcurrentException], як результат повертається 0, що вказує на те, що жоден рядок не було змінено чи видалено. Після цього DLL проекту генерується заново, розміщується в папці [test1], а тести NUnit запускаються знову:

Цього разу все гаразд. Відтепер ми будемо працювати з цим DLL.
2.5.6.9. Інтеграція нового шару [dao] у додаток [webarticles]
2.5.6.9.1. джерело даних ODBC
Тут ми тестуємо джерело даних ODBC, розглянуте в розділі 2.3.3.1. Воно використовується тут через SqlMap.
Ми дотримуємося підходу, описаного в розділі 2.3.4. Вносимо такі зміни до вмісту папки [runtime]:
- у папці [bin] файл DLL ізстарого шару [dao] замінюється на DLL з нового шару [dao], реалізованого класом [ArticlesDaoSqlMap]. Додаємо необхідні для Firebird шари DLL та SqlMap:

- у [runtime] розміщуємо конфігураційні файли SqlMap та [providers.config, sqlmap.config, properties.xml, articles.xml]:

- у [runtime] файл конфігурації [web.config] замінюється файлом, який враховує новий клас реалізації:
Коментарі:
- у рядку 14 до синглтона [articlesDao] прив’язується екземпляр нового класу [ArticlesDaoSqlMap]. Це єдина зміна.
Ми готові до тестування. Налаштовуємо веб-сервер [Cassini] так само, як і в попередніх тестах. Ініціалізуємо таблицю статей із такими значеннями:

За допомогою браузера ми звертаємося до URL [http://localhost/webarticles/main.aspx]:

![]() |
Тепер перевіримо вміст таблиці [ARTICLES]:

Артикули [couteau] та [cuiller] було придбано, а їхні запаси зменшено на кількість, що була придбана. Артикул [fourchette] не вдалося придбати, оскільки запитувана кількість перевищувала кількість на складі. Пропонуємо читачеві провести додаткові тести.
2.5.6.9.2. джерело даних MSDE
Тут ми тестуємо джерело даних MSDE, розглянуте в розділі 2.4.3.1. Воно використовується тут через SqlMap. Ми дотримуємося того самого підходу, що й раніше. Вносимо такі зміни до вмісту папки [runtime]:
- вміст папки [bin] не змінюється
- у [runtime] змінюються файли конфігурації SqlMap та [providers.config, properties.xml]. Файли конфігурації [sqlmap.config, articles.xml] не змінюються.
- Файл [providers.config] налаштовує новий <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>
Цей <provider> використовує класи .NET для доступу до джерел даних SQL Server. Він стандартно інтегрований у файл-шаблон [providers.config], що поширюється разом із SqlMap.
- Файл [properties.xml] визначає <provider> джерела MSDE, а також ланцюжок підключення до нього:
<?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>
- у [runtime] файл конфігурації [web.config] не змінюється.
Ми готові до тестування. Веб-сервер [Cassini] зберігає свою звичну конфігурацію. Ми ініціалізуємо таблицю статей джерела MSDE за допомогою [EMS MS SQL Manager]:

За допомогою браузера ми запитуємо URL [http://localhost/webarticles/main.aspx]:

![]() |
Тепер перевіримо вміст таблиці [ARTICLES] разом із [EMS MS SQL Manager]:

Артикули [ballon foot] та [raquette tennis] було придбано, а їхні запаси зменшено на кількість, що була придбана. Артикул [rollers] не вдалося придбати, оскільки запитувана кількість перевищувала кількість на складі. Пропонуємо читачеві провести додаткові тести.
2.5.6.9.3. джерело даних OleDb
Тут ми тестуємо джерело даних ACCESS, представлене в розділі 2.4.5.1. Воно використовується тут через SqlMap. Ми дотримуємося того самого підходу, що й раніше. Вносимо такі зміни до вмісту файлу [runtime]:
- вміст папки [bin] не змінюється
- у [runtime] змінюються файли конфігурації з SqlMap та [providers.config, properties.xml]. Файли конфігурації [sqlmap.config, articles.xml] не змінюються.
- Файл [providers.config] налаштовує новий <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>
Цей <provider> використовує класи .NET для доступу до джерел даних OleDb. Він стандартно інтегрований у файл-шаблон [providers.config], що поширюється разом із SqlMap.
- Файл [properties.xml] визначає <provider> джерела OleDb, а також ланцюжок підключення до нього:
<?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>
- у [runtime] файл конфігурації [web.config] не змінюється.
Ми готові до тестування. Веб-сервер [Cassini] зберігає свою звичну конфігурацію. Ми ініціалізуємо таблицю статей джерела ACCESS наступним чином:

За допомогою браузера ми звертаємося до URL [http://localhost/webarticles/main.aspx]:

![]() |
Тепер перевіримо вміст таблиці [ARTICLES] за допомогою:

Артикули [pantalon] та [jupe] було придбано, а їхні запаси зменшено на кількість, що була придбана. Артикул [manteau] не вдалося придбати, оскільки запитувана кількість перевищувала кількість на складі. Пропонуємо читачеві провести додаткові тести.
2.5.7. Висновок
На цьому ми завершуємо цю довгу статтю-посібник. Що ми зробили?
- Ми реалізували рівень [dao] трирівневого веб-додатка чотирма різними способами:
- використовуючи класи доступу .NET до джерел ODBC
- використовуючи класи доступу .NET до джерел SQL Server
- використовуючи класи доступу .NET до джерел OleDb
- використовуючи сторонні класи доступу для доступу до бази даних Firebird
- щоразу ми інтегрували новий рівень [dao] у трирівневу програму [webarticles] без перекомпіляції жодного з рівнів [web, domain, dao] [web, domain]
- нарешті ми представили інструмент [SqlMap], який дозволив нам створити шар [dao], здатний прозоро для коду адаптуватися до різних джерел даних. Таким чином, завдяки цьому новому шару ми змогли послідовно використовувати джерела даних з попередніх реалізацій 1–3. Це було зроблено прозоро за допомогою конфігураційних файлів.
- Ми продемонстрували високу гнучкість, яку інструменти Spring та SqlMap надають тришаровим веб-додаткам.































































