2. Parte 2
2.1. Introdução
Começaremos por rever o que foi feito na Parte 1, em particular a arquitetura de três camadas [web, domínio, DAO] utilizada. Na solução proposta, a camada [DAO] era uma camada de teste: a fonte de dados foi implementada utilizando um objeto [ArrayList]. Neste artigo, iremos centrar-nos na camada [DAO], apresentando várias implementações possíveis da mesma quando os dados se encontram num SGBD.
Ferramentas utilizadas:
- o SGBD Firebird — ver Apêndice, Secção 3.5.
- o SGBD MSDE (Microsoft Data Engine) — ver Apêndice, Secção 3.12.
- IBExpert, Edição Pessoal, para administração gráfica do SGBD Firebird — ver Apêndice, Secção 3.6.
- EMS MS SQL Manager para administração gráfica do SGBD MSDE — ver Apêndice, Secção 3.14.
- Ibatis SqlMap para a camada de acesso aos dados do SGBD — consulte a secção 2.5.6.2.
Numa escala iniciante-intermédio-avançado, este documento enquadra-se na categoria [intermédio-avançado]. A sua compreensão requer vários pré-requisitos. Alguns destes podem ser encontrados em documentos que escrevi. Nesses casos, faço referência aos mesmos. Escusado será dizer que isto é meramente uma sugestão e que o leitor é livre de utilizar os recursos da sua preferência.
- Linguagem VB.NET: [Introdução ao VB.NET através de exemplos]
- Programação Web em VB.NET: [Desenvolvimento Web com ASP.NET 1.1]
- Utilização do IoC do Spring: [Spring IoC para .NET]
- Documentação do iBatis SqlMap: [http://prdownloads.sourceforge.net/ibatisnet/DevGuide.pdf?download]
- Documentação do Firebird: [http://firebird.sourceforge.net/pdfmanual/Firebird-1.5-QuickStart.pdf]
- Documentação do Spring.net: [http://www.springframework.net/documentation.html]
2.2. Aplicação webarticles - Revisão
Apresentamos aqui os componentes da aplicação web de comércio eletrónico simplificada abordada na Parte 1. Esta aplicação permite aos utilizadores da web:
- visualizar uma lista de artigos de uma base de dados
- adicionar alguns deles a um carrinho de compras eletrónico
- confirmar o carrinho. Esta confirmação atualiza simplesmente os níveis de inventário dos itens adquiridos na base de dados.
2.2.1. Visualizações da aplicação
As diferentes vistas apresentadas ao utilizador são as seguintes:
![]() |
![]() |
![]() |
- a vista [ERRORS], que apresenta quaisquer erros da aplicação

2.2.2. Arquitetura geral da aplicação
A aplicação criada na Parte 1 tem uma arquitetura de três camadas:
![]() |
- As três camadas foram tornadas independentes através da utilização de interfaces
- A integração das diferentes camadas foi implementada utilizando o Spring
- Cada camada tem o seu próprio namespace: web (camada de interface do utilizador), domain (camada de negócios) e dao (camada de acesso a dados).
A aplicação segue uma arquitetura MVC (Model-View-Controller). Se nos referirmos ao diagrama em camadas acima, a arquitetura MVC encaixa-se nele da seguinte forma:
![]() |
O processamento de um pedido do cliente segue estes passos:
- O cliente envia uma solicitação ao controlador. Este controlador é, neste caso, uma página .aspx que desempenha uma função específica. Ele lida com todas as solicitações do cliente. É o ponto de entrada da aplicação. É o C em MVC.
- O controlador processa esta solicitação. Para tal, pode necessitar da assistência da camada de negócios, conhecida como o M na estrutura MVC.
- O controlador recebe uma resposta da camada de negócios. A solicitação do cliente foi processada. Isso pode desencadear várias respostas possíveis. Um exemplo clássico é
- uma página de erro, caso a solicitação não tenha sido processada corretamente
- uma página de confirmação, caso contrário
- O controlador escolhe a resposta (= vista) a enviar ao cliente. Trata-se, na maioria das vezes, de uma página que contém elementos dinâmicos. O controlador fornece estes à vista.
- A vista é enviada ao cliente. Esta é o V em MVC.
2.2.3. O Modelo
O M em MVC consiste nos seguintes elementos:
2.2.3.1. A base de dados
A base de dados contém apenas uma tabela chamada ARTICLES, gerada utilizando os seguintes comandos 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
);
/* constraints */
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);
/* primary key */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
chave primária que identifica um item de forma única | |
nome do item | |
o seu preço | |
stock atual | |
o nível de stock abaixo do qual deve ser efetuada uma nova encomenda |
2.2.3.2. Os namespaces do modelo
O Modelo M é fornecido na forma de dois espaços de nomes:
- istia.st.articles.dao: contém as classes de acesso a dados da camada [dao]
- istia.st.articles.domain: contém as classes de negócio da camada [domain]
Cada um destes namespaces está contido no seu próprio ficheiro «assembly»:
assembly | conteúdo | função |
webarticles-dao | - [IArticlesDao]: a interface para aceder à camada [dao]. Esta é a única interface visível para a camada [domain]. Não vê outras. - [Article]: classe que define um artigo - [ArticlesDaoArrayList]: classe de implementação da interface [IArticlesDao] utilizando uma classe [ArrayList] | camada de acesso a dados - localizada inteiramente dentro da camada [dao] da arquitetura de três camadas da aplicação web |
webarticles-domain | - [IArticlesDomain]: a interface para aceder à camada [domain]. É a única interface visível para a camada web. Não vê outras. - [ArticlePurchases]: uma classe que implementa [IArticlesDomain] - [Purchase]: uma classe que representa a compra de um cliente - [Cart]: uma classe que representa o total de compras de um cliente | representa o modelo de compra web - reside inteiramente na camada [domain] da arquitetura de 3 camadas da aplicação web |
2.2.4. Implantação e teste da aplicação [webarticles]
2.2.4.1. Implantação
Implementamos a aplicação desenvolvida na Parte 1 deste artigo numa pasta denominada [runtime]:
![]() | ![]() |
![]() |
Comentários:
A pasta [runtime] contém três ficheiros e duas subpastas:
- os controladores [global.asax] e [main.aspx]
- o ficheiro de configuração [web.config]
- a pasta [bin], que contém:
- as DLLs para as três camadas [webarticles-dao.dll], [webarticles-domain.dll], [webarticles-web.dll]
- os ficheiros necessários ao Spring [Spring-Core.*], [log4net.dll]
- a pasta [views], que contém o código de apresentação para as várias vistas.
- A presença de ficheiros de código .vb é desnecessária, uma vez que as suas versões compiladas se encontram nas DLLs.
2.2.4.2. Testes
Configuramos o servidor web [Cassini] da seguinte forma:

com:
Caminho físico: D:\data\serge\work\2004-2005\aspnet\webarticles-010405\runtime\
Caminho virtual: /webarticles
Utilizando um navegador, solicitamos o URL [http://localhost/webarticles/main.aspx]

Recorde-se que a camada [dao] é implementada por uma classe que armazena os artigos num objeto [ArrayList]. Esta classe cria uma lista inicial de quatro artigos. A partir da vista acima, utilizamos os links do menu para realizar operações. Aqui estão algumas delas. A coluna da esquerda representa o pedido do cliente e a coluna da direita representa a resposta enviada ao cliente.
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
2.2.5. A camada [DAO] revisitada
Na nossa primeira implementação da camada [dao], a interface de acesso a dados [IArticlesDao] foi implementada por uma classe que armazenava os itens num objeto [ArrayList]. Isto permitiu-nos evitar complicar excessivamente esta camada e demonstrar que apenas a sua interface era importante, e não a sua implementação. Conseguimos, assim, construir uma aplicação web funcional. Esta aplicação tem três camadas: [web], [domain] e [dao]. Aqui, iremos propor diferentes implementações da camada [dao]. Cada uma delas pode substituir a atual camada [dao] sem qualquer modificação nas camadas [domain] e [web]. Esta flexibilidade é alcançada porque:
- a camada [domain] não se refere a uma classe concreta, mas a uma interface [IArticlesDao]
- graças ao Spring, conseguimos ocultar o nome da classe que implementa a interface [IArticlesDao] da camada [domain].
2.2.5.1. Elementos da camada [dao]
Vamos rever alguns dos elementos da camada [dao] que serão mantidos nas novas implementações:
- - [IArticlesDao]: a interface para aceder à camada [dao]
- - [Article]: a classe que define um artigo
2.2.5.2. A classe [Article]
A classe que define um artigo é a seguinte:
Imports System
Namespace istia.st.articles.dao
Public Class Article
' private fields
Private _id As Integer
Private _nom As String
Private _prix As Double
Private _stockactuel As Integer
Private _stockminimum As Integer
' id article
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
' item name
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
' item price
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
' current stock item
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
' minimum stock item
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
' default builder
Public Sub New()
End Sub
' builder with properties
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
' article identification method
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
Esta classe fornece:
- um construtor para definir as 5 informações de um item: [id, nome, preço, stockAtual, stockMínimo]
- propriedades públicas para ler e gravar as 5 informações.
- uma validação dos dados introduzidos para o item. Se os dados forem inválidos, é lançada uma exceção.
- um método toString que devolve o valor de um item como uma cadeia de caracteres. Isto é frequentemente útil para depurar uma aplicação.
2.2.5.3. A interface [IArticlesDao]
A interface [IArticlesDao] é definida da seguinte forma:
Imports System
Imports System.Collections
Namespace istia.st.articles.dao
Public Interface IArticlesDao
' list of all items
Function getAllArticles() As IList
' add an article
Function ajouteArticle(ByVal unArticle As Article) As Integer
' deletes an article
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' modify an article
Function modifieArticle(ByVal unArticle As Article) As Integer
' search for an article
Function getArticleById(ByVal idArticle As Integer) As Article
' deletes all articles
Sub clearAllArticles()
' changes the stock of an item
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
As funções dos vários métodos na interface são as seguintes:
retorna todos os artigos da fonte de dados | |
limpa a fonte de dados | |
retorna o objeto [Article] identificado pelo seu número | |
permite adicionar um artigo à fonte de dados | |
permite modificar um artigo na fonte de dados | |
permite eliminar um item da fonte de dados | |
permite modificar o stock de um item na fonte de dados |
A interface fornece aos programas clientes vários métodos definidos exclusivamente pelas suas assinaturas. Não se preocupa com a forma como esses métodos serão efetivamente implementados. Isto confere flexibilidade a uma aplicação. O programa cliente efetua chamadas a uma interface, em vez de a uma implementação específica da mesma.
![]() |
A escolha de uma implementação específica é feita através de um ficheiro de configuração do Spring.
2.3. A classe de implementação [ArticlesDaoPlainODBC]
Propomos uma nova implementação da camada [dao] que pressupõe que os dados se encontram numa fonte ODBC. Sabemos que, no Windows, praticamente todos os SGBDs disponíveis no mercado possuem um controlador ODBC. A vantagem desta solução é que é possível alternar entre SGBDs de forma transparente para a aplicação. A desvantagem é que um controlador ODBC que apenas explora funcionalidades comuns a todos os SGBDs é, geralmente, menos eficiente do que um controlador escrito especificamente para explorar todo o potencial de um SGBD específico. Consulte a Secção 3.7 para ver um exemplo de criação de uma fonte ODBC.
2.3.1. O código
2.3.1.1. O esqueleto
A classe [ArticlesDaoPlainODBC] implementa a interface [IArticlesDao] da seguinte forma:
Comentários:
- Linha 3: importa o namespace que contém as classes .NET para aceder a fontes ODBC
- Linha 11 - irá armazenar a ligação à fonte ODBC
- Linha 12 - armazena o nome DSN da fonte de dados
- Linhas 13–19: variáveis privadas do tipo [OdbcCommand] que definem as consultas SQL utilizadas pelos vários métodos da classe
- Linhas 22–27: o construtor. Recebe os elementos necessários para construir o objeto [OdbcConnection] que ligará o código à fonte de dados ODBC
- linhas 29–31 – o método para adicionar um item
- linhas 33–35 – o método para alterar o stock de um item
- linhas 37–39 – o método que elimina todos os itens da fonte de dados ODBC
- linhas 41–43 – o método que recupera a lista de todos os itens da fonte ODBC
- linhas 45–47 – o método que recupera um item específico
- linhas 49-51 – o método que permite modificar determinados campos de um item cujo número se conhece
- linhas 53-55 – o método que permite eliminar um item pelo seu número
- linhas 57-60 – método utilitário para executar um [SELECT] na fonte de dados e devolver o resultado
- linhas 62–64 – método utilitário para executar um [INSERT, UPDATE, DELETE] na fonte de dados e devolver o resultado
2.3.1.2. O
Comentários:
- Linha 2 - O construtor recebe as três informações necessárias para se ligar a uma fonte ODBC: o nome DSN da fonte, o nome de utilizador para se ligar e a palavra-passe associada.
- Linha 8 - O nome DSN da fonte é armazenado para que possa ser incluído nas mensagens de erro.
- Linha 9 - O objeto [OdbcConnection] é instanciado. Uma conexão instanciada não é uma conexão aberta. O método [open] é usado para abrir a conexão.
- Linhas 12–19 – Preparamos as consultas SQL em objetos [OdbcCommand]. Isto evita que tenhamos de as reconstruir sempre que precisarmos delas. Os parâmetros formais ? nas consultas serão substituídos por valores reais quando a consulta for executada.
2.3.1.3. O método executeQuery
Comentários:
- O método [executeQuery] é um método utilitário que:
- executa uma consulta [SELECT id, name, price, currentStock, minimumStock from ARTICLES ...] na fonte de dados
- retorna o resultado como uma lista de objetos [Article]
- Linha 1 - O único parâmetro do método é o objeto [OdbcCommand] que contém a consulta [Select] a ser executada.
- Linha 7 - A ligação é aberta. Será fechada na linha 29, independentemente de ter ocorrido um erro ou não.
- Linha 9 - O objeto [OdbcDataReader] necessário para processar o resultado da consulta [Select] é instanciado
- Linhas 13–23 – Cada linha resultante da consulta [Select] é colocada num objeto [Article], que é então adicionado aos outros artigos numa [ArrayList]
- A lista de itens é devolvida na linha 25
- Não são tratadas exceções. Estas devem ser tratadas pelo código que chama este método.
2.3.1.4. O método executeUpdate
Comentários:
- O método recebe um objeto [OdbcCommand] que contém uma consulta SQL do tipo [Insert, Update, Delete].
- A ligação é aberta na linha 5. Será fechada na linha 10, independentemente de ter ocorrido ou não uma exceção.
- A consulta de atualização é executada na linha 7. O resultado — o número de linhas na tabela ARTICLES modificadas pela consulta — é imediatamente devolvido.
2.3.1.5. O método addArticle
Comentários:
- Linha 1 - O método recebe o item a ser adicionado à fonte de dados ODBC. Ele retorna o número de linhas afetadas por esta operação, ou seja, 1 ou 0
- Linhas 3 e 20 - O método é sincronizado. Este será o caso para todos os métodos de acesso a dados. Isto significa que apenas um thread de cada vez pode trabalhar na fonte de dados. Isto é provavelmente demasiado conservador. Existem alternativas melhores, nomeadamente incluir estas operações em transações. Neste caso, o SGBD gere o acesso simultâneo. Não quisemos introduzir o conceito de transações nesta fase. O Spring oferece-nos a opção de as introduzir na camada [domain]. Poderemos ter a oportunidade de revisitar este tema noutro artigo.
- As linhas 5–12 atribuem valores aos parâmetros formais da consulta para o objeto [insertCommand] inicializado pelo construtor. Aqui está a consulta novamente:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
Os 5 valores necessários para a consulta são fornecidos pelas linhas 7–11.
- Linhas 13–19: A consulta é executada. Se for bem-sucedida, o resultado é devolvido. Caso contrário, é lançada uma exceção genérica com uma mensagem de erro explícita
2.3.1.6. O método modifieArticle
Comentários:
- Linha 1 - O método recebe o item a ser modificado da fonte de dados ODBC. Ele retorna o número de linhas afetadas por esta operação, ou seja, 1 ou 0
- Os comentários do método [addItem] podem ser incluídos aqui
2.3.1.7. O método deleteArticle
Comentários:
- Linha 1 - O método recebe o ID do item a ser eliminado da fonte de dados ODBC. Devolve o número de linhas afetadas por esta operação, ou seja, 1 ou 0
- Os comentários para o método [addItem] podem ser incluídos aqui
2.3.1.8. O método getAllArticles
Comentários:
- Linha 1 - O método não recebe parâmetros. Ele retorna uma lista de todos os itens da fonte de dados ODBC
- A consulta [Select] que recupera todos os registos é passada para o método [executeQuery] - linha 6
- A lista resultante é devolvida na linha 8
- As linhas 9–12 tratam de quaisquer exceções
2.3.1.9. O método getArticleById
Comentários:
- Linha 1 - O método recebe o ID do item pretendido como parâmetro. Devolve o item se este for encontrado na fonte ODBC; caso contrário, devolve a referência [nothing].
- A consulta [Select] que solicita o item é inicializada nas linhas 5–8
- e é executada na linha 12 - é obtida uma lista de itens
- se esta lista estiver vazia, a referência [nothing] é devolvida na linha 14
- Caso contrário, o único item da lista é apresentado na linha 16
- As linhas 17–20 tratam de quaisquer exceções
2.3.1.10. O método clearAllArticles
Comentários:
- Linha 1 - O método não recebe parâmetros e não retorna nada
- Linha 6 - A consulta para eliminar todos os itens é executada
- Linhas 7–10: Tratar quaisquer exceções
2.3.1.11. O método changerStockArticle
Comentários:
- Linha 1 - O método recebe como parâmetros o número do item cujo stock precisa de ser modificado, bem como o incremento do stock (positivo ou negativo). Devolve o número de linhas modificadas pela operação, ou seja, 0 ou 1.
- Linhas 5–10: A consulta [updateStockCommand] é inicializada. Aqui está o texto da consulta SQL:
updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
Note que o stock só é modificado se, após a alteração, permanecer >=0.
- A consulta para atualizar o stock do artigo é executada na linha 13, e o resultado é devolvido
- nas linhas 14–18, onde tratamos quaisquer exceções
2.3.2. Gerando a montagem da camada [DAO]
O projeto do Visual Studio para esta nova versão da camada [dao] tem a seguinte estrutura:

O projeto está configurado para gerar uma DLL chamada [webarticles-dao.dll]:
![]() | ![]() |
2.3.3. Testes NUnit para a camada [dao]
2.3.3.1. Criação de uma fonte de dados ODBC-Firebird no
Para testar a nossa nova camada [DAO], precisamos de uma fonte de dados ODBC e, portanto, de uma base de dados. Estamos a utilizar o SGBD Firebird (Secção 3.5). Utilizando o IBExpert (Secção 3.6), criamos a seguinte base de dados de artigos:
![]() | ![]() |
O administrador desta base de dados será o utilizador [SYSDBA] com a palavra-passe [masterkey]. Criamos alguns registos:

Criamos agora a seguinte fonte ODBC do Firebird (ver secção 3.7):
![]() |
A fonte ODBC criada tem as seguintes características:
- Nome do DSN: odbc-firebird-articles
- ID de ligação: SYSDBA
- senha associada: masterkey
2.3.3.2. A classe de teste NUnit para a camada [dao]
Já escrevemos uma classe de teste para a camada [dao] inicialmente construída. Se o leitor se lembra, esta classe testava não uma classe específica, mas a interface [IArticlesDao]:
Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO
Namespace istia.st.articles.tests
<TestFixture()> _
Public Class NunitTestArticlesArrayList
' the test object
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' retrieve an instance of the Spring object manufacturer
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' request instantiation of articlesdao object
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
....
Podemos ver que, no método <Setup()>, solicitamos ao Spring uma referência ao singleton denominado [articlesdao] do tipo [IArticlesDao], ou seja, do tipo de interface. O singleton [articlesdao] foi definido pelo seguinte ficheiro de configuração [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>
Vamos demonstrar que a classe de teste inicial nos permite testar a nossa nova camada [dao] sem modificações ou recompilação.
- Na pasta do Visual Studio para a nossa nova camada [dao], crie a pasta [tests] (mostrada à direita abaixo) copiando a pasta [bin] do projeto de teste da camada [dao] inicial (mostrada à esquerda abaixo). Se necessário, convidamos o leitor a rever o projeto de teste da primeira versão da camada [dao] na primeira parte do artigo.
![]() | ![]() |
- Na pasta [tests], substitua a DLL [webarticles-dao.dll] da antiga camada [dao] pela DLL [webarticles-dao.dll] da nova camada [dao]
- Modifique o ficheiro de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoPlainODBC]:
Comentários:
- na linha 6, o objeto [articlesdao] está agora associado a uma instância da classe [ArticlesDaoPlainODBC]
- Esta classe tem um construtor com três argumentos:
- o nome da fonte DSN - linha 8
- a identidade utilizada para aceder à base de dados - linha 11
- a palavra-passe associada a esta identidade - linha 14
Aqui, estamos a utilizar as informações da fonte ODBC-Firebird que criámos anteriormente.
2.3.3.3. Testes
Estamos agora prontos para executar os testes. Utilizando a aplicação [Nunit-Gui], carregamos a DLL [test-webarticles-dao.dll] da pasta [tests] acima e executamos o teste [testGetAllArticles]:

Olhando para a captura de ecrã acima, podemos arrepender-nos do nome [NUnitTestArticlesDaoArrayList] inicialmente atribuído à classe de teste. É confuso. Na verdade, é a classe [ArticlesDaoPlainODBC] que está a ser testada aqui. A captura de ecrã mostra que recuperámos corretamente os artigos que colocámos na tabela [ARTICLES]. Agora, vamos executar todos os testes:

Na janela da esquerda, vemos a lista de métodos testados. A cor do ponto que precede o nome de cada método indica se o método passou (verde) ou falhou (vermelho). Os leitores que estiverem a visualizar este documento no ecrã verão que todos os testes foram bem-sucedidos.
2.3.3.4. Conclusão
Acabámos de demonstrar que:
- porque a classe de teste NUnit não referenciava uma classe, mas sim uma interface;
- porque o nome exato da classe que instanciava a interface foi fornecido num ficheiro de configuração e não no código;
- porque o Spring tratou da instanciação da classe e forneceu uma referência à mesma no código de teste;
por isso, o código de teste escrito para a camada [dao] inicial permaneceu válido para uma nova implementação dessa mesma camada. Não precisámos de aceder ao código da classe de teste. Utilizámos apenas a sua versão compilada, aquela gerada durante o teste da camada [dao] inicial. Chegaremos a conclusões semelhantes quando chegar a altura de integrar a nova camada [dao] na aplicação [webarticles].
2.3.4. Integração da nova camada [dao] na aplicação [webarticles]
2.3.4.1. Testes de integração
Lembre-se de que a versão inicial da aplicação [webarticles] foi implementada na seguinte pasta [runtime]:
![]() | ![]() |
![]() |
Recomenda-se aos leitores que consultem a Secção 2.2.4, que detalha os procedimentos de implementação da aplicação [webarticles]. Efetuaremos as seguintes alterações ao conteúdo da pasta [runtime]:
- Na pasta [bin], a DLL da antiga camada [dao] é substituída pela DLL da nova camada [dao]
- em [runtime], o ficheiro de configuração [web.config] é substituído por um ficheiro que tem em conta a nova classe de implementação da camada [dao]:
![]() |
![]() |
O novo ficheiro de configuração [web.config] é o seguinte:
Comentários:
- As linhas 14–24 associam o singleton [articlesDao] a uma instância da nova classe [ArticlesDaoPlainODBC]. Esta é a única alteração. Já nos deparámos com isto durante os testes da nova camada [dao].
Estamos prontos para os testes. Configuramos o servidor web [Cassini] da mesma forma que na secção 2.2.4. Inicializamos a tabela de produtos [Firebird] com os seguintes valores:

Certifique-se de que o servidor web Cassini e o SGBD [Firebird] estão em execução. Utilizando um navegador, acedemos ao URL [http://localhost/webarticles/main.aspx]:

![]() |
Agora vamos verificar o conteúdo da tabela [ARTICLES] na base de dados [Firebird]:

Os artigos [guarda-chuva] e [botas] foram comprados, e os seus níveis de stock foram reduzidos pela quantidade comprada. O artigo [chapéu] não pôde ser comprado porque a quantidade solicitada excedeu a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.3.4.2. Conclusão
O que fizemos?
- Revertemos para a versão de implementação da versão anterior;
- substituímos a DLL da camada [dao] por uma nova versão. As DLLs das camadas [web] e [domain] permaneceram inalteradas;
- modificámos o ficheiro de configuração [web.config] para que este tenha em conta a nova classe de implementação da camada [dao]
Tudo isto é simples e facilita muito a evolução da aplicação web. Estas características importantes são proporcionadas por duas escolhas arquitetónicas:
- acesso às camadas através de interfaces
- integração e configuração das camadas pelo Spring.
Estamos agora a propor uma nova implementação da camada [dao].
2.4. A classe de implementação [ArticlesDaoSqlServer]
A segunda implementação da camada [dao] pressupõe que os dados se encontram numa base de dados SQL Server. A Microsoft fornece um SGBD denominado MSDE, que é uma versão limitada do SQL Server. Consulte o apêndice para obter instruções sobre como o obter e instalar, secção 3.12.
2.4.1. O código
A classe [ArticlesDaoSqlServer] é muito semelhante à classe [ArticlesDaoPlainODBC] discutida anteriormente. Por isso, iremos apenas destacar as alterações feitas à versão anterior:
- as classes necessárias estão no namespace [System.Data.SqlClient] em vez do namespace [System.Data.Odbc]
- a ligação [OdbcConnection] é agora do tipo [SqlConnection]
- os objetos [OdbcCommand] são agora do tipo [SqlCommand]
- a sintaxe das consultas SQL parametrizadas mudou. A consulta de inserção tem agora o seguinte aspeto:
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
enquanto anteriormente era:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
- O método [addItem] passa então a ser o seguinte:
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' exclusive section
SyncLock Me
' prepare the insertion request
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
'it is executed
Return executeUpdate(insertCommand)
Catch ex As Exception
'query error
Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
End Try
End SyncLock
End Function
- O construtor também é modificado:
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: instance name SQL server to reach
' databaseName: name of the database to be reached
' uid: user identity
' password: your password
'retrieve the name of the database passed as an argument
Me.databaseName = databaseName
'we instantiate the connection
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' prepare SQL requests
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
...
End Sub
O construtor aceita agora quatro parâmetros:
' server: instance name SQL server to reach
' databaseName: name of the database to be reached
' uid: user identity
' password: your password
O código completo para a classe [ArticlesDaoSqlServer] é o seguinte:
Imports System
Imports System.Collections
Imports System.Data.SqlClient
Namespace istia.st.articles.dao
Public Class ArticlesDaoSqlServer
Implements istia.st.articles.dao.IArticlesDao
' private fields
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
' manufacturer
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: instance name SQL server to reach
' databaseName: name of the database to be reached
' uid: user identity
' password: your password
'retrieve the name of the database passed as an argument
Me.databaseName = databaseName
'we instantiate the connection
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' prepare SQL requests
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
' exclusive section
SyncLock Me
' prepare the insertion request
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
'it is executed
Return executeUpdate(insertCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the stock update request
With updateStockCommand.Parameters
.Clear()
.Add(New SqlParameter("@mvt", mouvement))
.Add(New SqlParameter("@id", idArticle))
End With
'it is executed
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the insertion request
executeUpdate(deleteAllCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the select query
Dim articles As IList = executeQuery(selectAllCommand)
'we return the list
Return articles
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the select query
With selectSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'it is executed
Try
'execute the query
Dim articles As IList = executeQuery(selectSomeCommand)
'we test if we've found the article
If articles.Count = 0 Then Return Nothing
'we return the item
Return CType(articles.Item(0), Article)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the update request
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
' it is executed
Try
'execute the insertion request
Return executeUpdate(updatecommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the delete request
With deleteSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'it is executed
Try
'execute the delete request
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'query error
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
' query execution SELECT
' declaration of the object providing access to all rows in the result table
Dim myReader As SqlDataReader = Nothing
Try
'create a connection to BDD
connexion.Open()
'execute the query
myReader = query.ExecuteReader()
'declare a list of items and return it later
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'we prepare an article with the reader's values
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)
'add the item to the list
articles.Add(unArticle)
End While
'returns the result
Return articles
Finally
' freeing up resources
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
' execute an update request
Try
'create a connection to BDD
connexion.Open()
'execute the query
Return updateCommand.ExecuteNonQuery()
Finally
' freeing up resources
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Recomenda-se ao leitor que analise este código à luz dos comentários sobre a classe [ArticlesDaoPlainODBC] apresentados anteriormente.
2.4.2. Gerar a compilação da camada [dao]
O novo projeto do Visual Studio tem a seguinte estrutura:

O projeto está configurado para gerar uma DLL denominada [webarticles-dao.dll]:
![]() | ![]() |
2.4.3. Testes NUnit para a camada [dao]
2.4.3.1. Criação de uma fonte de dados do SQL Server
Para testar a nossa nova camada [dao], precisamos de uma fonte de dados do SQL Server e, por conseguinte, do SGBD SQL Server. Na prática, utilizaremos o SGBD MSDE (Microsoft Data Engine) (Secção 3.12), que é uma versão do SQL Server limitada apenas pelo número de utilizadores simultâneos que suporta. Utilizando o [EMS MS SQL Manager] (secção 3.14), criamos a seguinte base de dados de produtos numa instância do MSDE denominada [portable1_tahe\msde140405]:
![]() | ![]() |

A base de dados pertence ao utilizador [mdparticles] com a palavra-passe [admarticles]. O comando Transact-SQL para criar a tabela [ARTICLES] é o seguinte:
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
Criamos alguns itens:

2.4.3.2. A classe de teste NUnit
A classe de teste NUnit para a classe de implementação [ArticlesDaoSqlServer] é idêntica à da classe [ArticlesDaoPlainODBC] (ver secção 2.3.3.2). Seguimos uma abordagem semelhante para preparar o teste NUnit para a classe:
- criamos a pasta [tests] (à direita) na pasta do Visual Studio do projeto [dao-sqlserver], copiando a pasta [tests] do projeto [dao-odbc] (à esquerda):
![]() | ![]() |
- Na pasta [tests] do projeto [dao-sqlserver], substituímos a DLL [webarticles-dao.dll] pela DLL [webarticles-dao.dll] gerada pelo projeto [dao-sqlserver]
- Modificamos o ficheiro de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoSqlServer]:
Comentários:
- na linha 7, o objeto [articlesdao] está agora associado a uma instância da classe [ArticlesDaoSqlServer]
- Esta classe possui um construtor com quatro argumentos:
- o nome da instância MSDE utilizada - linha 9
- o nome da base de dados - linha 12
- a identidade utilizada para aceder à base de dados - linha 15
- a palavra-passe associada a esta identidade - linha 18
Aqui estamos a utilizar as informações da fonte MSDE que criámos anteriormente.
2.4.3.3. Testes
Estamos prontos para executar os testes. Utilizando a aplicação [Nunit-Gui], carregamos a DLL [test-webarticles-dao.dll] da pasta [tests] acima e executamos o teste [testGetAllArticles]:

Embora a classe de teste tenha sido inicialmente denominada [NUnitTestArticlesDaoArrayList] — um nome mantido porque estamos a utilizar a DLL [tests-webarticles-dao.dll] derivada desta classe —, é de facto a classe [ArticlesDaoSqlserver] que está a ser testada aqui. A captura de ecrã mostra que recuperámos corretamente os artigos que colocámos na tabela [ARTICLES]. Agora, vamos executar todos os testes:

Na janela à esquerda, vemos a lista de métodos testados. A cor do ponto que precede o nome de cada método indica se o método passou (verde) ou falhou (vermelho). Os leitores que estiverem a visualizar este documento no ecrã verão que todos os testes foram bem-sucedidos.
2.4.4. Integrar a nova camada [dao] na aplicação [webarticles]
Seguimos o procedimento explicado na secção 2.3.4. Efetuamos as seguintes alterações ao conteúdo da pasta [runtime]:
- Na pasta [bin], a DLL da antiga camada [dao] é substituída pela DLL da nova camada [dao] implementada pela classe [ArticlesDaoSqlServer]
- Em [runtime], o ficheiro de configuração [web.config] é substituído por um ficheiro que tem em conta a nova classe de implementação:
Comentários:
- As linhas 15–33 associam o singleton [articlesDao] a uma instância da nova classe [ArticlesDaoSqlServer]. Esta é a única alteração. Já nos deparámos com isto durante os testes da nova camada [dao]
Estamos prontos para o teste. Mantemos a mesma configuração do servidor web [Cassini] de antes. Inicializamos a tabela de produtos [MSDE] com os seguintes valores:

Certifique-se de que o servidor web Cassini e o SGBD MSDE (aqui, a instância portable1_tahe\msde140405) estão em execução. Utilizando um navegador, solicitamos a URL [http://localhost/webarticles/main.aspx]:

![]() |
Agora, vamos verificar o conteúdo da tabela [ARTICLES] na base de dados [MSDE]:

Os artigos [bola de futebol] e [raquete de ténis] foram comprados, e os seus níveis de stock foram reduzidos pela quantidade comprada. O artigo [patins em linha] não pôde ser comprado porque a quantidade solicitada excedeu a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.4.5. A classe de implementação [ArticlesDaoOleDb]
2.4.5.1. de fonte de dados OleDb
A terceira implementação da camada [dao] pressupõe que os dados se encontram numa base de dados acessível através de um controlador OleDb. O princípio subjacente às fontes OleDb é análogo ao das fontes ODBC. Um programa que utilize uma fonte OleDb fá-lo através de uma interface padrão comum a todas as fontes OleDb. Alterar a fonte OleDb implica simplesmente alterar o controlador OleDb. O código em si permanece inalterado.
Pode descobrir quais os controladores OleDb disponíveis no seu computador utilizando o Visual Studio:
- exiba o Server Explorer selecionando [View/Server Explorer]:

- Para adicionar uma nova ligação, clique com o botão direito do rato em [Ligação de Dados] e selecione a opção [Adicionar Ligação]. Isto abre um assistente onde pode definir as definições de ligação:

- O painel [Provedor] lista os controladores OLEDB disponíveis. Para a nova camada [DAO], utilizaremos o controlador [Microsoft Jet 4.0 OLE DB Provider], que fornece acesso a bases de dados Access.
- Vamos sair temporariamente do Visual Studio para criar a base de dados ACCESS [articles.mdb] com a seguinte tabela única:

- A estrutura da tabela é a seguinte:
numérico - inteiro - chave primária | |
texto - 20 caracteres - | |
numérico - duplo | |
numérico - inteiro | |
numérico - inteiro |
- Voltemos ao Visual Studio e criemos uma nova ligação, tal como explicado anteriormente:

- Selecionamos o controlador [Microsoft Jet 4.0] e vamos para o painel [Conexão]:

- Usando o botão [1], selecione a base de dados ACCESS que acabou de ser criada e, em seguida, conclua a configuração da ligação clicando no botão [Concluir]. A ligação que criou aparece agora na lista de ligações disponíveis:

- Ao clicar duas vezes na tabela [ARTICLES], temos acesso ao seu conteúdo:

- Pode então adicionar, modificar ou eliminar linhas na tabela.
- No Explorador de Servidores, selecione a nova ligação para aceder à sua ficha Propriedades:

- É útil saber a cadeia de ligação. Vamos utilizá-la para nos ligarmos à base de dados:
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
- Desta cadeia de caracteres, iremos reter apenas os seguintes elementos:
2.4.5.2. O código da classe [ArticlesDaoOleDb]
A classe [ArticlesDaoOleDb] é muito semelhante à classe [ArticlesDaoPlainODBC] discutida anteriormente. Por isso, iremos apenas listar as alterações feitas em relação à versão anterior:
- as classes necessárias estão no namespace [System.Data.OleDb] em vez do namespace [System.Data.Odbc]
- a ligação [OdbcConnection] é agora do tipo [OleDbConnection]
- os objetos [OdbcCommand] são agora do tipo [OleDbCommand]
O construtor da classe aceita um único parâmetro: a cadeia de ligação à base de dados:
' manufacturer
Public Sub New(ByVal connectString As String)
' connectString: source connection string OleDb: source connection string connectString: source connection string OleDb: source connection string
'we instantiate the connection
connexion = New OleDbConnection(connectString)
' prepare SQL requests
...
End Sub
O código completo para a classe [ArticlesDaoOleDb] é o seguinte:
Imports System
Imports System.Collections
Imports System.Data.OleDb
Namespace istia.st.articles.dao
Public Class ArticlesDaoOleDb
Implements istia.st.articles.dao.IArticlesDao
' private fields
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
' manufacturer
Public Sub New(ByVal connectString As String)
' connectString: source connection string OleDb: source connection string connectString: source connection string OleDb: source connection string
'we instantiate the connection
connexion = New OleDbConnection(connectString)
' prepare SQL requests
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
' exclusive section
SyncLock Me
' prepare the insertion request
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
'it is executed
Return executeUpdate(insertCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the stock update request
With updateStockCommand.Parameters
.Clear()
.Add(New OleDbParameter("mvt1", mouvement))
.Add(New OleDbParameter("id", idArticle))
.Add(New OleDbParameter("mvt2", mouvement))
End With
'it is executed
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the insertion request
executeUpdate(deleteAllCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the select query
Dim articles As IList = executeQuery(selectAllCommand)
'we return the list
Return articles
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the select query
With selectSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'it is executed
Try
'execute the query
Dim articles As IList = executeQuery(selectSomeCommand)
'we test if we've found the article
If articles.Count = 0 Then Return Nothing
'we return the item
Return CType(articles.Item(0), Article)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the update request
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
' it is executed
Try
'execute the insertion request
Return executeUpdate(updatecommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the delete request
With deleteSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'it is executed
Try
'execute the delete request
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'query error
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
' query execution SELECT
' declaration of the object providing access to all rows in the result table
Dim myReader As OleDbDataReader = Nothing
Try
'create a connection to BDD
connexion.Open()
'execute the query
myReader = query.ExecuteReader()
'declare a list of items and return it later
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'we prepare an article with the reader's values
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)
'add the item to the list
articles.Add(unArticle)
End While
'returns the result
Return articles
Finally
' freeing up resources
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
' execute an update request
Try
'create a connection to BDD
connexion.Open()
'execute the query
Return sqlCommand.ExecuteNonQuery()
Finally
' freeing up resources
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Recomenda-se ao leitor que analise este código à luz dos comentários sobre a classe [ArticlesDaoPlainODBC] apresentados anteriormente.
2.4.5.3. Gerar a compilação da camada [dao]
O novo projeto do Visual Studio tem a seguinte estrutura:

O projeto está configurado para gerar uma DLL denominada [webarticles-dao.dll]:
![]() | ![]() |
2.4.5.4. Testes NUnit para a camada [dao]
2.4.5.4.1. A classe de teste NUnit
A classe de teste NUnit para a classe de implementação [ArticlesDaoOleDb] é a mesma que a da classe [ArticlesDaoPlainODBC] (ver secção 2.3.3.2). Seguimos uma abordagem semelhante para preparar o teste NUnit para a classe:
- criamos a pasta [tests] (à direita) na pasta do Visual Studio do projeto [dao-oledb], copiando a pasta [tests] do projeto [dao-odbc] (à esquerda):
![]() | ![]() |
- Na pasta [tests] do projeto [dao-oledb], substituímos a DLL [webarticles-dao.dll] pela DLL [webarticles-dao.dll] gerada pelo projeto [dao-oledb]
- Modificamos o ficheiro de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoOleDb]:
Comentários:
- na linha 7, o objeto [articlesdao] está agora associado a uma instância da classe [ArticlesDaoOleDb]
- Esta classe tem um construtor com um argumento: a cadeia de ligação à base de dados OleDb ACCESS - linha 9
2.4.5.4.2. Testes
Estamos prontos para testar. Utilizando a aplicação [Nunit-Gui], carregamos a DLL [test-webarticles-dao.dll] da pasta [tests] acima e executamos o teste [testGetAllArticles]:

Apesar do nome [NUnitTestArticlesDaoArrayList] inicialmente atribuído à classe de teste, é de facto a classe [ArticlesDaoOleDb] que está a ser testada aqui. A captura de ecrã mostra que recuperámos corretamente os artigos que colocámos na tabela [ARTICLES]. Agora, vamos executar todos os testes:

Os leitores que estiverem a visualizar este documento no ecrã verão que todos os testes foram aprovados (cor verde).
2.4.5.5. Integrar a nova camada [dao] na aplicação [webarticles]
Seguimos o procedimento explicado na secção 2.3.4. Efetuamos as seguintes alterações ao conteúdo da pasta [runtime]:
- Na pasta [bin], a DLL da antiga camada [dao] é substituída pela DLL da nova camada [dao] implementada pela classe [ArticlesDaoOleDb]
- Em [runtime], o ficheiro de configuração [web.config] é substituído por um ficheiro que tem em conta a nova classe de implementação:
Comentários:
- As linhas 14–18 associam o singleton [articlesDao] a uma instância da nova classe [ArticlesDaoOleDb]. Esta é a única alteração.
Mantemos a mesma configuração do servidor web [Cassini] de antes. Inicializamos a tabela de produtos com os seguintes valores:

Certifique-se de que a base de dados de artigos não está a ser utilizada por um programa como o Visual Studio ou o Access. Utilizando um navegador, solicitamos o URL [http://localhost/webarticles/main.aspx]:

![]() |
Agora, vamos verificar o conteúdo da tabela [ARTICLES] utilizando o Access:

Os artigos [calças] e [saia] foram comprados e os seus níveis de stock foram reduzidos pela quantidade comprada. O artigo [casaco] não pôde ser comprado porque a quantidade solicitada excedeu a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.5. A classe de implementação [ArticlesDaoFirebirdProvider]
2.5.1. O Firebird-net-provider
Anteriormente, utilizámos uma fonte de dados [Firebird] através de um controlador ODBC. Embora os controladores ODBC ofereçam elevada reutilização para o código que os utiliza, são menos eficientes do que os controladores escritos especificamente para o SGBD de destino. O SGBD [Firebird] pode ser utilizado através de uma biblioteca de classes específicas que pode ser descarregada a partir do site do Firebird [http://firebird.sourceforge.net/]. A página de downloads disponibiliza as seguintes ligações (abril de 2005):
![]()
O link [firebird-net-provider] é o que deve ser utilizado para descarregar as classes .NET para aceder ao SGBD Firebird. A instalação do pacote cria uma pasta semelhante à seguinte:

Dois itens são do nosso interesse:
- [FirebirdSql.Data.Firebird.dll]: o assembly que contém as classes .NET para aceder ao SGBD Firebird
- [FirebirdNETProviderSDK.chm]: a documentação para estas classes
Em seguida, para permitir que um projeto do Visual Studio utilize estas classes, faremos duas coisas:
- Colocaremos o assembly [FirebirdSql.Data.Firebird.dll] na pasta [bin] do projeto
- adicionar este mesmo assembly às referências do projeto
2.5.2. O código da classe [ArticlesDaoFirebirdProvider]
A classe [ArticlesDaoFirebirdProvider] é muito semelhante à classe [ArticlesDaoSqlServer] discutida anteriormente. Por isso, iremos apenas destacar as alterações feitas em comparação com essa versão:
- As classes necessárias estão no namespace [FirebirdSql.Data.Firebird] em vez do namespace [System.Data.SqlClient]
- A ligação [SqlConnection] é agora do tipo [FbConnection]
- Os objetos [SqlCommand] são agora do tipo [FbCommand]
- Os objetos [SqlParameter] são agora do tipo [FbParameter]
O construtor da classe aceita quatro parâmetros, que utiliza para construir a cadeia de ligação à base de dados:
' manufacturer
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' server: name of the SGBD host machine
' databaseName: path to database
' uid: identity of the user logging in
' password: your password
...
End Sub
O código completo para a classe [ArticlesDaoFirebirdProvider] é o seguinte:
Imports System
Imports System.Collections
Imports FirebirdSql.Data.Firebird
Namespace istia.st.articles.dao
Public Class ArticlesDaoFirebirdProvider
Implements istia.st.articles.dao.IArticlesDao
' private fields
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
' manufacturer
Public Sub New(ByVal serveur As String, ByVal databasePath As String, ByVal uid As String, ByVal password As String)
' server: name of the SGBD Firebird host machine
' databaseName: path to the database to be used
' uid: identity of the user connecting to the database
' password: your password
'retrieve the name of the database passed as an argument
Me.databasePath = databasePath
'we instantiate the connection
Dim connectString As String = String.Format("DataSource={0};Database={1};User={2};Password={3}", serveur, databasePath, uid, password)
connexion = New FbConnection(connectString)
' prepare SQL requests
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
' exclusive section
SyncLock Me
' prepare the insertion request
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
'it is executed
Return executeUpdate(insertCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the stock update request
With updateStockCommand.Parameters
.Clear()
.Add(New FbParameter("@mvt", mouvement))
.Add(New FbParameter("@id", idArticle))
End With
'it is executed
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the insertion request
executeUpdate(deleteAllCommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
Try
'execute the select query
Dim articles As IList = executeQuery(selectAllCommand)
'we return the list
Return articles
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the select query
With selectSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'it is executed
Try
'execute the query
Dim articles As IList = executeQuery(selectSomeCommand)
'we test if we've found the article
If articles.Count = 0 Then Return Nothing
'we return the item
Return CType(articles.Item(0), Article)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the update request
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
' it is executed
Try
'execute the insertion request
Return executeUpdate(updatecommand)
Catch ex As Exception
'query error
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
' exclusive section
SyncLock Me
' prepare the delete request
With deleteSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'it is executed
Try
'execute the delete request
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'query error
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
' query execution SELECT
' declaration of the object providing access to all rows in the result table
Dim myReader As FbDataReader = Nothing
Try
'create a connection to BDD
connexion.Open()
'execute the query
myReader = query.ExecuteReader()
'declare a list of items and return it later
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'we prepare an article with the reader's values
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)
'add the item to the list
articles.Add(unArticle)
End While
'returns the result
Return articles
Finally
' freeing up resources
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
' execute an update request
Try
'create a connection to BDD
connexion.Open()
'execute the query
Return updateCommand.ExecuteNonQuery()
Finally
' freeing up resources
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Recomenda-se ao leitor que analise este código à luz dos comentários sobre a classe [ArticlesDaoSqlServer] apresentados anteriormente.
2.5.3. Gerar a compilação da camada [dao]
O novo projeto do Visual Studio tem a seguinte estrutura:

Repare na presença do assembly [FirebirdSql.Data.Firebird.dll] nas referências do projeto. Esta DLL foi colocada na pasta [bin] do projeto. O projeto está configurado para gerar uma DLL denominada [webarticles-dao.dll]:
![]() | ![]() |
2.5.4. Testes NUnit para a camada [dao]
2.5.4.1. A classe de teste NUnit
A classe de teste NUnit para a classe de implementação [ArticlesDaoFirebirdProvider] é idêntica à da classe [ArticlesDaoPlainODBC] (ver secção 2.3.3.2). Seguimos uma abordagem semelhante para preparar o teste NUnit para a classe [ArticlesDaoFirebirdProvider]:
- criamos a pasta [tests] (à direita) na pasta do Visual Studio do projeto [dao-firebird-provider] copiando a pasta [bin] do projeto de teste da camada [dao-odbc] (à esquerda):
![]() | ![]() |
- Na pasta [tests], substituímos a DLL [webarticles-dao.dll] pela DLL [webarticles-dao.dll] gerada a partir do projeto [dao-firebird-provider]
- Modificamos o ficheiro de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoFirebirdProvider]:
Comentários:
- na linha 7, o objeto [articlesdao] está agora associado a uma instância da classe [ArticlesDaoFirebirdProvider]
- esta classe tem um construtor com quatro argumentos
- a máquina anfitriã do SGBD - linha 9
- o caminho para a base de dados Firebird - linha 12
- o nome de utilizador do utilizador que se está a ligar - linha 15
- a sua palavra-passe - linha 18
2.5.4.2. Testes
A tabela [ARTICLES] na fonte de dados é preenchida com os seguintes itens (utilize o IBExpert):

Estamos prontos para executar os testes. Utilizando a aplicação [Nunit-Gui], carregamos a DLL [test-webarticles-dao.dll] da pasta [tests] acima e executamos o teste [testGetAllArticles]:

Apesar do nome [NUnitTestArticlesDaoArrayList] inicialmente atribuído à classe de teste, é de facto a classe [ArticlesDaoFirebirdProvider] que está a ser testada aqui. A captura de ecrã mostra que recuperámos corretamente os artigos que colocámos na tabela [ARTICLES]. Agora, vamos executar todos os testes:

Os leitores que estiverem a visualizar este documento no ecrã verão que todos os testes foram aprovados (verde). O que não conseguem ver é que os testes foram executados significativamente mais rápido do que com a base de dados de artigos acedida através de um controlador ODBC na nossa primeira implementação.
2.5.5. Integrar a nova camada [dao] na aplicação [webarticles]
Seguimos o procedimento já explicado duas vezes, nomeadamente na secção 2.3.4. Efetuamos as seguintes alterações ao conteúdo da pasta [runtime]:
- Na pasta [bin], a DLL da antiga camada [dao] é substituída pela DLL da nova camada [dao] implementada pela classe [ArticlesDaoFirebirdProvider]. Colocamos também aí a DLL necessária para o Firebird [FirebirdSql.Data.Firebird.dll]:

- Em [runtime], o ficheiro de configuração [web.config] é substituído por um ficheiro que tem em conta a nova classe de implementação:
Comentários:
- As linhas 14–27 associam o singleton [articlesDao] a uma instância da nova classe [ArticlesDaoFirebirdProvider]. Esta é a única alteração.
Estamos prontos para o teste. Configuramos o servidor web [Cassini] tal como nos testes anteriores. Inicializamos a tabela articles com os seguintes valores:

Usando um navegador, acedemos ao URL [http://localhost/webarticles/main.aspx]:

![]() |
Agora vamos verificar o conteúdo da tabela [ARTICLES]:

Os artigos [lápis] e [bloco de 50 folhas] foram comprados e os seus níveis de stock foram reduzidos pela quantidade comprada. O artigo [caneta de tinta permanente] não pôde ser comprado porque a quantidade solicitada excedeu a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.5.6. A classe de implementação [ArticlesDaoSqlMap]
2.5.6.1. O produto Ibatis SqlMap
Escrevemos quatro implementações diferentes da camada [dao] para a nossa aplicação [webarticles]. Em cada caso, conseguimos integrar a nova camada [dao] na aplicação [webarticles] sem recompilar as outras duas camadas, [web] e [domain]. Isto foi conseguido, como lembrete, através de duas escolhas arquitetónicas:
- acesso às camadas através de interfaces
- integração das camadas utilizando o Spring
Queremos levar isto um passo mais além. Embora diferentes, as nossas quatro implementações da camada [dao] partilham semelhanças marcantes. Depois de escrita a primeira implementação, as outras três foram criadas quase inteiramente através de copiar e colar e substituir certas palavras-chave por outras. A lógica, no entanto, permaneceu inalterada. Poder-se-ia questionar se seria possível ter uma única implementação que nos libertasse dos vários métodos de acesso aos dados. Utilizámos quatro:
- acesso através de um controlador ODBC a uma fonte de dados ODBC
- acesso direto a uma base de dados SQL Server
- acesso através de um controlador Ole DB a uma fonte de dados Ole DB
- acesso direto a uma base de dados Firebird
A ferramenta Ibatis SqlMap [[http://www.ibatis.com/]] permite o desenvolvimento de camadas de acesso a dados independentes da natureza concreta da fonte de dados. O acesso aos dados é assegurado através de:
- ficheiros de configuração que contêm informações que definem a fonte de dados e as operações a realizar na mesma
- uma biblioteca de classes que utiliza estas informações para aceder aos dados
A ferramenta Ibatis SqlMap foi inicialmente desenvolvida para a plataforma Java. A sua portabilidade para a plataforma .NET é recente e parece apresentar alguns erros (opinião pessoal que exigiria uma verificação aprofundada). No entanto, uma vez que a ferramenta já deu provas da sua eficácia na plataforma Java, parece valer a pena apresentar a versão .NET.
2.5.6.2. Onde posso encontrar o IBATIS SqlMap ?
O site principal do Firebird é [http://www.ibatis.com/]. A página de downloads oferece os seguintes links:

Selecione o link [Stable Binaries], que o levará para [SourceForge.net]. Siga o processo de download até ao fim. Receberá um ficheiro ZIP contendo os seguintes ficheiros:

Num projeto do Visual Studio que utilize o iBatis SqlMap, é necessário fazer duas coisas:
- colocar os ficheiros acima referidos na pasta [bin] do projeto
- adicionar uma referência a cada um destes ficheiros ao projeto
2.5.6.3. Ficheiros de configuração do iBatis SqlMap
Uma fonte de dados [SqlMap] será definida utilizando os seguintes ficheiros de configuração:
- providers.config: define as bibliotecas de classes a utilizar para aceder aos dados
- sqlmap.config: define as configurações de ligação
- Ficheiros de mapeamento: definem as operações a realizar nos dados
A lógica por trás destes ficheiros é a seguinte:
- Para aceder aos dados, precisaremos de uma ligação. Para representar isto, já nos deparámos com várias classes: OdbcConnection, SqlConnection, OleDbConnection, FbConnection. Também precisaremos de um objeto [Command] para emitir consultas SQL: OdbcCommand, SqlCommand, OleDbCommand, FbCommand, etc. No ficheiro [providers.config], definimos todas as classes de que precisamos.
- O ficheiro [sqlmap.config] define essencialmente a cadeia de ligação à base de dados que contém os dados. A ligação à base de dados será aberta através da instanciação da classe [Connection] definida em [providers.config], cujo construtor receberá a cadeia de ligação definida em [sqlmap.config].
- Os ficheiros de mapeamento definem:
- associações entre linhas nas tabelas de dados e classes .NET, cujas instâncias conterão essas linhas
- as operações SQL a serem executadas. Estas são identificadas por um nome. O código .NET executa estas operações através dos seus nomes, o que elimina todo o código SQL do código .NET.
2.5.6.4. Os ficheiros de configuração para o projeto [dao-sqlmap]
Vamos examinar a natureza exata dos ficheiros de configuração do SqlMap usando um exemplo. Consideraremos o caso em que a fonte de dados é a fonte ODBC do Firebird da secção 2.3.3.1.
2.5.6.4.1. providers.config
O ficheiro [providers.config] para uma fonte ODBC é o seguinte:
Comentários:
- Um ficheiro [providers.config] está incluído no pacote [SqlMap]. Este oferece vários fornecedores padrão. O código acima foi retirado diretamente deste ficheiro.
- Um <provider> tem um nome — linha 6 — que pode ser qualquer coisa
- Um <provider> pode ser ativado [enabled=true] ou desativado [enabled=false]. Se estiver ativado, a DLL referenciada na linha 8 deve estar acessível. Um ficheiro [providers.config] pode conter várias tags <provider>.
- linha 8 - nome do assembly que contém as classes definidas nas linhas 9-15
- linha 9 - classe a utilizar para criar uma ligação
- linha 10 - classe a utilizar para criar um objeto [Command] para a execução de comandos SQL
- linha 11 - classe a utilizar para gerir os parâmetros de um comando SQL parametrizado
- linha 12 - classe de enumeração dos tipos de dados possíveis para os campos da tabela
- linha 13 - nome da propriedade de um objeto [Parameter] que contém o tipo do valor deste parâmetro
- linha 14 - nome da classe [Adapter] utilizada para criar objetos [DataSet] a partir da fonte de dados
- linha 15 - nome da classe [CommandBuilder] que, quando associada a um objeto [Adapter], gera automaticamente as suas propriedades [InsertCommand, DeleteCommand, UpdateCommand] a partir da sua propriedade [SelectCommand]
- linhas 16–19 – definem como os comandos SQL parametrizados são tratados. Dependendo da situação, pode escrever, por exemplo:
ou
No primeiro caso, trata-se de parâmetros posicionais formais. Os seus valores reais devem ser fornecidos na ordem dos parâmetros formais. No segundo caso, trata-se de parâmetros nomeados. Um valor é fornecido para um parâmetro deste tipo especificando o seu nome. A ordem já não importa.
- Linha 16 – indica que as fontes ODBC utilizam parâmetros posicionais
- Linhas 17–19 – dizem respeito a parâmetros nomeados. Não há nenhum aqui.
Esta informação permite ao SqlMap saber, por exemplo, qual a classe que deve instanciar para criar uma ligação. Aqui, será a classe [OdbcConnection] (linha 9).
2.5.6.4.2. sqlmap.config
O ficheiro [providers.config] define as classes a utilizar para aceder a uma fonte ODBC. Não especifica quaisquer fontes ODBC. O ficheiro [sqlmap.config] faz isso:
Comentários:
- Linha 3 - Definimos um ficheiro de propriedades [properties.xml]. Este ficheiro define pares chave-valor. As chaves podem ser qualquer coisa. O valor associado a uma chave C é obtido utilizando a notação ${C} em [sqlmap.config]. Aqui está o ficheiro [properties.xml] que será associado ao ficheiro [sqlmap.config] anterior:
Linha 3 - a chave [provider] é definida. O seu valor é o nome da tag <provider> a ser utilizada em [providers.config]
linha 4 - a chave [connectionString] é definida. O seu valor é a cadeia de ligação a utilizar para estabelecer uma ligação à fonte de dados ODBC do Firebird.
- linhas 4-7 - parâmetros de configuração:
- linha 5 - as consultas SQL serão identificadas por um nome que pode, por sua vez, fazer parte de um namespace. [useStatementNamespaces="false"] indica que os namespaces não serão utilizados.
- linha 6 - O SqlMap possui várias estratégias de cache para minimizar o acesso à fonte de dados. [cacheModelsEnabled="false"] indica que nenhuma será utilizada.
- Linhas 9–13 – As propriedades da fonte de dados são definidas:
- linha 10 - nome do <provider> de [providers.config] a utilizar
- Linha 11 - Cadeia de ligação à fonte de dados
- linha 12 - gestor de transações. Não o utilizámos aqui, mas mantivemos a linha porque estava no ficheiro de distribuição padrão.
- linhas 14-16 – lista de ficheiros que definem as operações SQL a serem executadas na fonte de dados.
- linha 15 - define o ficheiro de mapeamento [articles.xml]
2.5.6.4.3. articles.xml
Este ficheiro tem duas finalidades:
- Definir um mapeamento de objetos para as tabelas da fonte de dados. Nos casos mais simples, isto equivale a associar uma classe a uma linha numa tabela.
- Definir operações SQL parametrizadas e nomeá-las.
Iremos utilizar o seguinte ficheiro [articles.xml]:
Comentários:
- Linhas 4-11 - Definimos um mapeamento entre uma linha na tabela [ARTICLES] da fonte de dados e a classe [istia.st.articles.dao.Article]. Cada coluna da tabela está associada a uma propriedade da classe [Article]. Este mapeamento permite que o [SqlMap] construa o resultado de uma operação SQL SELECT. Cada linha de resultado da SELECT será colocada num objeto [Article] de acordo com as regras de mapeamento.
- Linha 5 - O mapeamento está entre as tags <resultMap> e é nomeado através do atributo [id="article"]. A classe associada é especificada pelo atributo [class="istia.st.articles.dao.Article"].
- Linhas 14–44 – As operações SQL necessárias são definidas
- Linhas 16–18 – É definida uma operação SELECT denominada [getAllArticles]
- linha 16 - a operação SELECT é nomeada [name="getAllArticles"] e o mapeamento a utilizar é definido pelo atributo [resultMap="article"]. Isto refere-se ao mapeamento nas linhas 5–11
- linha 17 – texto do comando SQL a ser executado
- Linhas 20–22 – Definimos o comando SQL-Delete [clearAllArticles] para limpar a tabela de artigos.
- Linhas 24–27 – Definimos o comando SQL-Insert [insertArticle] para adicionar um novo item à tabela de itens. Esta é uma consulta parametrizada que utiliza os elementos (#id#, #name#, #price#, #currentStock#, #minStock#). Os valores para estes cinco elementos provêm de um objeto [Article] passado como parâmetro: [parameterClass="istia.st.articles.dao.Article"]. O objeto parâmetro deve possuir as propriedades (id, name, price, currentStock, minimumStock) referenciadas pelo comando SQL parametrizado.
- linhas 29-31 - definimos o comando SQL Delete [deleteArticle] destinado a eliminar um artigo cujo número #value# é conhecido. Este número será passado como parâmetro: [parameterClass="int"]. Esta é uma regra geral. Quando o parâmetro é único, é referenciado pela palavra-chave #value# no texto do comando SQL.
- Linhas 33–35 – Definimos o comando SQL-Update [modifyArticle] para modificar um artigo cujo número é conhecido. Tal como no comando [insertArticle], as cinco informações necessárias provêm das propriedades de um objeto [istia.st.articles.dao.Article].
- Linhas 37–39 – Definimos o comando SQL-Select [getArticleById], que recupera o registo de um artigo cujo número é conhecido.
- Linhas 41–43 – Definimos o comando SQL-Update [changerStockArticle], que modifica o campo [stockactuel] de um item cujo número é conhecido. As duas informações necessárias — o #id# do item e o incremento de stock #mouvement# — serão encontradas num dicionário: [parameterClass="Hashtable"]. Este dicionário deve ter duas chaves: id e mouvement. Os valores associados a estas duas chaves serão utilizados no comando SQL.
2.5.6.4.4. Localização dos ficheiros de configuração
Vamos considerar dois cenários diferentes:
- No caso de um teste Nunit, os ficheiros de configuração [SqlMap] serão colocados na mesma pasta que os binários testados.
- No caso de uma aplicação web, serão colocados na raiz da aplicação.
2.5.6.5. A API do SqlMap
As classes SqlMap estão contidas em DLLs que são normalmente colocadas na pasta [bin] da aplicação:

As aplicações que utilizam classes SqlMap devem importar o namespace [IBatisNet.DataMapper]:
Todas as operações SQL são realizadas através de um singleton do tipo [Mapper], uma classe no namespace [IBatisNet.DataMapper]. O singleton é obtido da seguinte forma:
Para executar o comando SqlMap [getAllArticles], escrevemos:
- O método [QueryForList] devolve o resultado de um comando SELECT como uma lista
- O primeiro parâmetro é o nome do comando SQL a ser executado (ver articles.xml)
- O segundo parâmetro é o parâmetro a passar para a consulta SQL. Deve corresponder ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass=Nothing]. Por isso, passamos aqui um ponteiro nulo.
- O resultado é do tipo IList. Os objetos nesta lista são especificados pelo atributo [resultMap] do comando SQL-select: [resultMap="article"]. "article" é um nome de mapeamento:
A classe associada a este mapeamento é [istia.st.articles.dao.Article]. Em última análise, a variável [articles] definida acima é uma lista de objetos [istia.st.articles.dao.Article]. Assim, recuperámos toda a tabela [ARTICLES] numa única instrução. Se a tabela [ARTICLES] estiver vazia, obtemos um objeto [IList] com 0 elementos.
Para executar o comando SqlMap [getArticleById], escrevemos:
- O método [QueryForObject] recupera o resultado de um comando SELECT que devolve apenas uma linha
- O primeiro parâmetro é o nome do comando SqlMap a ser executado
- O segundo parâmetro é o parâmetro a passar para a consulta SQL. Deve corresponder ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass="int"]. Por isso, passamos aqui um inteiro que representa o ID do artigo que está a ser pesquisado.
- O resultado é do tipo Object. Se o SELECT não devolveu nenhuma linha, o resultado é um ponteiro nulo (nada).
Para executar o comando SqlMap [insertArticle], escrevemos:
- O método [Insert] permite executar comandos SQL INSERT
- O primeiro parâmetro é o nome do comando SqlMap a ser executado
- O segundo parâmetro é o parâmetro a ser passado para ele. Deve corresponder ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass="istia.st.articles.dao.Article"]. Portanto, passamos aqui um objeto do tipo [istia.st.articles.dao.Article].
Para executar o comando SqlMap [deleteArticle], escrevemos:
- O método [Delete] permite executar comandos SQL DELETE
- O primeiro parâmetro é o nome do comando SQL a executar
- O segundo parâmetro é o parâmetro a ser passado para ele. Deve corresponder ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass="int"]. Portanto, passamos aqui o ID do artigo a ser eliminado.
- O resultado do método [Delete] é o número de linhas eliminadas
Da mesma forma, para executar o comando SqlMap [clearAllArticles], escrevemos:
Para executar o comando SqlMap [modifyArticle], escrevemos:
- O método [Update] permite executar comandos SQL UPDATE
- O primeiro parâmetro é o nome do comando SqlMap a ser executado
- O segundo parâmetro é o parâmetro a ser passado para ele. Deve corresponder ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass="istia.st.articles.dao.Article"]. Portanto, passamos aqui um objeto do tipo [istia.st.articles.dao.Article].
- O resultado do método [Update] é o número de linhas modificadas.
Da mesma forma, para executar o comando SqlMap [changerStockArticle], escreveríamos:
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' update
dim nbLignes as Integer= mappeur.Update("changerStockArticle", paramètres)
- O segundo parâmetro corresponde ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass="Hashtable"]. O comando SQL parametrizado [changeItemStock] utiliza os parâmetros [id, movement]. Por isso, passamos aqui um dicionário com estas duas chaves.
2.5.6.6. O código para a classe [ArticlesDaoSqlMap]
Seguindo as explicações anteriores, podemos agora escrever a seguinte nova classe de implementação [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
' private fields
Dim mappeur As SqlMapper = Mapper.Instance
' list of all items
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
' add an item
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
SyncLock Me
Try
' unArticle : item to add
' insertion
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
' deletes an article
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
SyncLock Me
Try
' id : id of item to be deleted
' delete
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
' modify an article
Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
SyncLock Me
Try
' update
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
' article search
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
SyncLock Me
Try
' id: id of the item searched for
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
' delete all items
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
' change the stock of an item
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
SyncLock Me
Try
' id: id of the item whose stock is being changed
' movement: stock movement
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' update
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
Recomenda-se aos leitores que analisem este código à luz das explicações fornecidas para a API SqlMap. Vale a pena notar que a utilização do [SqlMap] reduziu significativamente a quantidade de código necessária.
2.5.6.7. Gerar a compilação da camada [dao]
O novo projeto do Visual Studio tem a seguinte estrutura:

Repare na presença dos «assemblys» exigidos pelo SqlMap nas referências do projeto. Estas DLLs foram colocadas na pasta [bin] do projeto. O projeto está configurado para gerar uma DLL denominada [webarticles-dao.dll]:
![]() | ![]() |
2.5.6.8. Testes NUnit para a camada [dao]
2.5.6.8.1. A classe de teste NUnit
A classe de teste NUnit para a classe de implementação [ArticlesDaoSqlMap] é idêntica à da classe [ArticlesDaoPlainODBC] (ver secção 2.3.3.2). Seguimos uma abordagem semelhante para preparar o teste NUnit para a classe [ArticlesDaoSqlMap]:
- criamos a pasta [test1] (à direita) na pasta do Visual Studio do projeto [dao-sqlmap], copiando a pasta [tests] do projeto [dao-odbc] (à esquerda):
![]() | ![]() |
- Na pasta [tests], substituímos a DLL [webarticles-dao.dll] pela DLL [webarticles-dao.dll] gerada a partir do projeto [dao-sqlmap].
- Adicionamos as DLLs necessárias ao SqlMap, bem como os ficheiros de configuração mencionados [providers.config, sqlmap.config, properties.xml, articles.xml].
- Modificamos o ficheiro de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoSqlMap]:
Comentários:
- Linha 7: O objeto [articlesdao] está agora associado a uma instância da classe [ArticlesDaoSqlMap]
- Esta classe não tem construtor. Será utilizado o construtor padrão.
2.5.6.8.2. Testes
A tabela [ARTICLES] na fonte de dados Firebird é preenchida com os seguintes artigos:

Estamos prontos para testar. Utilizando a aplicação [Nunit-Gui], carregamos a DLL [test-webarticles-dao.dll] da pasta [test1] acima e executamos o teste [testGetAllArticles]:

Apesar do nome [NUnitTestArticlesDaoArrayList] inicialmente atribuído à classe de teste, é de facto a classe [ArticlesDaoSqlMap] que está a ser testada aqui. A captura de ecrã mostra que recuperámos corretamente os artigos que colocámos na tabela [ARTICLES]. Agora, vamos executar todos os testes:

Os leitores que estiverem a visualizar este documento no ecrã verão que alguns testes foram aprovados (verde), mas outros falharam (vermelho). Os testes que falharam são [testArticleAbsent] e [testChangerStockArticle]. Após uma investigação exaustiva, parece que as causas destas falhas são as seguintes:
- Em [testArticleAbsent], tentamos modificar um item que não existe. Utilizamos o método [modifieArticle] para isso, que retorna o número de linhas modificadas como 0 ou 1. Aqui, deveríamos obter 0. Em vez disso, obtemos uma exceção do tipo [IBatisNet.Common.Exceptions.ConcurrentException].
- Em [changerStockArticle], existe outra operação do tipo [update]. Esta envolve a redução do stock numa quantidade superior ao stock atual. Para o fazer, utiliza-se o método [changerStockArticle], que devolve o número de linhas modificadas como 0 ou 1. O comando SQL foi escrito para impedir uma atualização (ver o comando SQL «changerStockArticle» em articles.xml) que resultaria num nível de stock negativo. Aqui, esperamos obter 0 como resultado do método [changerStockArticle]. Mais uma vez, obtemos uma exceção do tipo [IBatisNet.Common.Exceptions.ConcurrentException].
Existem muitas fontes possíveis de erro:
- o código na classe [ArticlesDaoSqlMap] está incorreto. Isso é possível. No entanto, provém de uma portabilidade de uma classe Java que funcionava corretamente com a versão Java do SqlMap.
- a versão .NET do SqlMap tem erros
- o controlador ODBC do Firebird tem erros
- ...
Na ausência de certezas, vamos contornar o problema capturando a [IBatisNet.Common.Exceptions.ConcurrentException]. O novo código para a classe [ArticlesDaoSqlMap] passa a ser o seguinte:
As alterações estão nas linhas: 28, 41, 69. Para operações SQL do tipo [UPDATE, DELETE], se ocorrer uma exceção do tipo [IBatisNet.Common.Exceptions.ConcurrentException], é devolvido 0 como resultado, indicando assim que nenhuma linha foi modificada ou eliminada. Depois de concluído, a DLL do projeto é regenerada, colocada na pasta [test1] e os testes NUnit são executados novamente:

Desta vez funciona. Vamos agora trabalhar com esta DLL.
2.5.6.9. Integrar a nova camada [dao] na aplicação [webarticles]
2.5.6.9.1. Fonte de dados ODBC
Aqui testamos a fonte de dados ODBC discutida na secção 2.3.3.1. É utilizada aqui através do SqlMap.
Seguimos o procedimento descrito na secção 2.3.4. Efetuamos as seguintes alterações ao conteúdo da pasta [runtime]:
- Na pasta [bin], a DLL da antiga camada [dao] é substituída pela DLL da nova camada [dao] implementada pela classe [ArticlesDaoSqlMap]. Adicionamos as DLLs necessárias para o Firebird e o SqlMap:

- em [runtime], colocamos os ficheiros de configuração do SqlMap [providers.config, sqlmap.config, properties.xml, articles.xml]:

- Em [runtime], o ficheiro de configuração [web.config] é substituído por um ficheiro que tem em conta a nova classe de implementação:
Comentários:
- As linhas 14 associam o singleton [articlesDao] a uma instância da nova classe [ArticlesDaoSqlMap]. Esta é a única alteração.
Estamos prontos para executar os testes. Vamos configurar o servidor web [Cassini] tal como fizemos nos testes anteriores. Vamos preencher a tabela articles com os seguintes valores:

Usando um navegador, solicitamos a URL [http://localhost/webarticles/main.aspx]:

![]() |
Agora vamos verificar o conteúdo da tabela [ARTICLES]:

Os itens [faca] e [colher] foram comprados e os seus níveis de stock foram reduzidos pela quantidade comprada. O item [garfo] não pôde ser comprado porque a quantidade solicitada excedeu a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.5.6.9.2. Fonte de dados MSDE
Aqui estamos a testar a fonte de dados MSDE discutida na secção 2.4.3.1. É utilizada aqui através do SqlMap. Seguimos o mesmo procedimento de antes. Fazemos as seguintes alterações ao conteúdo da pasta [runtime]:
- o conteúdo da pasta [bin] permanece inalterado
- Em [runtime], os ficheiros de configuração do SqlMap [providers.config, properties.xml] são alterados. Os ficheiros de configuração [sqlmap.config, articles.xml] permanecem inalterados.
- O ficheiro [providers.config] configura um novo <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>
Este <provider> utiliza as classes .NET para aceder a fontes de dados do SQL Server. Está incluído por predefinição no ficheiro de modelo [providers.config] distribuído com o SqlMap.
- O ficheiro [properties.xml] define o <provider> para a fonte MSDE, bem como a sua cadeia de ligação:
<?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>
- No [runtime], o ficheiro de configuração [web.config] permanece inalterado.
Estamos prontos para o teste. O servidor web [Cassini] mantém a sua configuração habitual. Inicializamos a tabela de artigos na fonte MSDE utilizando o [EMS MS SQL Manager]:

Utilizando um navegador, solicitamos o URL [http://localhost/webarticles/main.aspx]:

![]() |
Agora, vamos verificar o conteúdo da tabela [ARTICLES] utilizando o [EMS MS SQL Manager]:

Os artigos [bola de futebol] e [raquete de ténis] foram comprados e os seus níveis de stock foram reduzidos pela quantidade comprada. O artigo [patins em linha] não pôde ser comprado porque a quantidade solicitada excedeu a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.5.6.9.3. Fonte de dados OleDb
Aqui estamos a testar a fonte de dados ACCESS apresentada na secção 2.4.5.1. É utilizada aqui através do SqlMap. Seguimos o mesmo procedimento de antes. Fazemos as seguintes alterações ao conteúdo da pasta [runtime]:
- o conteúdo da pasta [bin] permanece inalterado
- Em [runtime], os ficheiros de configuração do SqlMap [providers.config, properties.xml] são alterados. Os ficheiros de configuração [sqlmap.config, articles.xml] permanecem inalterados.
- O ficheiro [providers.config] configura um novo <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>
Este <provider> utiliza as classes .NET para aceder a fontes de dados OleDb. Está incluído por predefinição no ficheiro de modelo [providers.config] distribuído com o SqlMap.
- O ficheiro [properties.xml] define o <provider> da fonte OleDb e a sua cadeia de ligação:
<?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>
- Em [runtime], o ficheiro de configuração [web.config] permanece inalterado.
Estamos prontos para o teste. O servidor web [Cassini] mantém a sua configuração habitual. Inicializamos a tabela de artigos a partir da fonte ACCESS da seguinte forma:

Utilizando um navegador, solicitamos o URL [http://localhost/webarticles/main.aspx]:

![]() |
Agora, vamos verificar o conteúdo da tabela [ARTICLES] com:

Os artigos [calças] e [saia] foram comprados e os seus níveis de stock foram reduzidos pela quantidade comprada. O artigo [casaco] não pôde ser comprado porque a quantidade solicitada excedeu a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.5.7. Conclusão
Concluímos aqui este longo artigo tutorial. O que fizemos?
- Implementámos a camada [DAO] de uma aplicação web de três camadas de quatro formas diferentes:
- utilizando classes de acesso .NET a fontes ODBC
- utilizando classes de acesso .NET para fontes SQL Server
- utilizando classes de acesso .NET para fontes OleDb
- utilizando classes de acesso de terceiros para aceder a uma base de dados Firebird
- Em cada caso, integramos a nova camada [DAO] na aplicação [webarticles] de três camadas [web, domínio, DAO] sem recompilar nenhuma das camadas [web, domínio]
- Introduzimos finalmente a ferramenta [SqlMap], que nos permitiu criar uma camada [DAO] capaz de se adaptar a diferentes fontes de dados de forma transparente para o código. Assim, com esta nova camada, conseguimos utilizar sucessivamente as fontes de dados das implementações anteriores 1 a 3. Isto foi feito de forma transparente utilizando ficheiros de configuração.
- Demonstrámos a grande flexibilidade que as ferramentas Spring e SqlMap proporcionam às aplicações web de três camadas.































































