2. Parte 2
2.1. Introduction
Começaremos por recordar o que foi feito na parte 1 e, nomeadamente, a arquitetura de três camadas [web, domain, dao] utilizada. Na solução proposta, a camada [dao] era uma camada de teste: a fonte de dados era implementada por um objeto [ArrayList]. Neste artigo, vamos 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 anexo, parágrafo 3.5.
- o SGBD MSDE (Microsoft Data Engine) — ver anexo, parágrafo 3.12.
- IBExpert, edição pessoal para a administração gráfica do SGBD Firebird — ver anexo, parágrafo 3.6.
- EMS MS SQL Manager para a administração gráfica do SGBD MSDE - ver anexo, parágrafo 3.14.
- Ibatis SqlMap para a camada de acesso aos dados do SGBD — ver parágrafo 2.5.6.2.
Numa escala que vai de principiante a intermédio a avançado, este documento situa-se na parte [intermédiaire-avancé]. A sua compreensão requer vários pré-requisitos. Alguns deles podem ser adquiridos em documentos que escrevi. Nesse caso, faço referência aos mesmos. É evidente que se trata apenas de uma sugestão e que o leitor pode recorrer aos seus documentos preferidos.
- linguagem VB.net: [Introduction au langage VB.NET par l'exemple ]
- programação web em VB.net: [Développement WEB avec ASP.NET 1.1 ]
- utilização do aspeto IoC do Spring: [Spring IoC pour .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 Spring.net: [http://www.springframework.net/documentation.html]
2.2. A aplicação webarticles - Resumo
Apresentamos aqui os elementos da aplicação web simplificada de comércio eletrónico estudada na parte 1. Esta permite aos clientes da web:
- consultar uma lista de artigos provenientes de uma base de dados
- colocar alguns deles num cesto de compras eletrónico
- confirmar o cesto. Esta confirmação tem como único efeito atualizar, na base de dados, os stocks dos artigos comprados.
2.2.1. As vistas da aplicação
As diferentes vistas apresentadas ao utilizador são as seguintes:
![]() |
![]() |
![]() |
- a vista [ERREURS], que sinaliza qualquer erro da aplicação

2.2.2. Arquitetura geral da aplicação
A aplicação desenvolvida na Parte 1 apresenta uma arquitetura de três camadas:
![]() |
- as três camadas foram tornadas independentes graças à utilização de interfaces
- a integração das diferentes camadas foi realizada com o Spring
- cada camada está sujeita a espaços de nomes separados: web (camada UI), domain (camada de negócio) e dao (camada de acesso aos dados).
A aplicação segue uma arquitetura MVC (Modelo - Vista - Controlador). Se retomarmos o esquema em camadas acima, a arquitetura MVC integra-se nele da seguinte forma:
![]() |
O processamento de um pedido de um cliente decorre de acordo com as seguintes etapas:
- o cliente envia um pedido ao controlador. Este controlador é, neste caso, uma página .aspx à qual é atribuída uma função específica. Ela recebe todos os pedidos dos clientes. É a porta de entrada da aplicação. É o C de MVC.
- o controlador processa essa solicitação. Para tal, pode necessitar da ajuda da camada de negócio, a que se chama modelo M na estrutura MVC.
- O controlador recebe uma resposta da camada de negócio. O pedido do cliente foi processado. Este pode dar origem a várias respostas possíveis. Um exemplo clássico é
- uma página de erros, caso a solicitação não tenha podido ser processada corretamente
- uma página de confirmação, caso contrário
- o controlador escolhe a resposta (= vista) a enviar ao cliente. Esta é, na maioria das vezes, uma página que contém elementos dinâmicos. O controlador fornece esses elementos à vista.
- A vista é enviada ao cliente. É o V de MVC.
2.2.3. O modelo
O modelo M do MVC é aqui constituído pelos seguintes elementos:
- as classes de negócio
- as classes de acesso aos dados
- a base de dados
2.2.3.1. A base de dados
A base de dados contém apenas uma tabela denominada ARTICLES, gerada com 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
);
/* restrições */
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);
/* chave primária */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
chave primária que identifica um artigo de forma única | |
nome do artigo | |
o seu preço | |
o seu stock atual | |
o nível de stock abaixo do qual deve ser efetuada uma encomenda de reabastecimento |
2.2.3.2. Os espaços de nomes do modelo
O modelo M é fornecido sob a forma de dois espaços de nomes:
- istia.st.articles.dao: contém as classes de acesso aos dados da camada [dao]
- istia.st.articles.domain: contém as classes de negócio da camada [domain]
Cada um destes espaços de nomes está contido num ficheiro «assembly» que lhe é próprio:
assembly | contenu | rôle |
webarticles-dao | - [IArticlesDao]: a interface de acesso à camada [dao]. É a única interface que a camada [domain] vê. Não vê nenhuma outra. - [Article]: classe que define um artigo - [ArticlesDaoArrayList]: classe de implementação da interface [IArticlesDao] com uma classe [ArrayList] | camada de acesso aos dados — encontra-se inteiramente na camada [dao] da arquitetura de três camadas da aplicação web |
webarticles-domain | - [IArticlesDomain]: a interface de acesso à camada [domain]. É a única interface que a camada web vê. Não vê nenhuma outra. - [AchatsArticles]: uma classe que implementa [IArticlesDomain] - [Achat]: classe que representa a compra de um cliente - [Panier]: classe que representa o conjunto de compras de um cliente | representa o modelo das compras na Web - encontra-se inteiramente na camada [domain] da arquitetura de três camadas da aplicação Web |
2.2.4. Implementação e testes da aplicação [webarticles]
2.2.4.1. Implantação
Implantamos a aplicação desenvolvida na parte 1 do artigo numa pasta chamada [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:
- os ficheiros DLL das três camadas [webarticles-dao.dll], [webarticles-domain.dll] e [webarticles-web.dll]
- os ficheiros necessários para o Spring: [Spring-Core.*], [log4net.dll]
- a pasta [vues], que contém o código de apresentação das diferentes vistas.
- A presença dos ficheiros de código .vb é desnecessária, uma vez que a sua versão compilada se encontra nos DLL.
2.2.4.2. Testes
Configuramos o servidor web [Cassini] da seguinte forma:

com:
Caminho físico: D:\data\serge\trabalho\2004-2005\aspnet\webarticles-010405\runtime\
Caminho virtual: /webarticles
Através de um navegador, acedemos ao 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 a resposta que lhe é dada.
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
2.2.5. A camada [dao] revista
Na nossa primeira implementação da camada [dao], a interface [IArticlesDao] de acesso aos dados tinha sido implementada por uma classe que armazenava os artigos num objeto [ArrayList]. Isto permitiu-nos não nos debruçarmos sobre esta camada e demonstrar que o que importava era apenas a sua interface e não a sua implementação. Assim, conseguimos construir uma aplicação web operacional. Esta possui três camadas: [web], [domain] e [dao]. Vamos apresentar aqui diferentes implementações da camada [dao]. Cada uma delas poderá substituir a camada atual [dao] sem qualquer alteração nas camadas [domain] e [web]. Esta flexibilidade é obtida porque:
- a camada [domain] não se refere a uma classe concreta, mas sim a uma interface [IArticlesDao]
- graças ao Spring, conseguimos ocultar à camada [domain] o nome da classe de implementação da interface [IArticlesDao].
2.2.5.1. Elementos da camada [dao]
Recordemos alguns dos elementos da camada [dao] que serão mantidos nas novas implementações:
- - [IArticlesDao]: a interface de acesso à camada [dao]
- - [Article]: 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
' campos privados
Private _id As Integer
Private _nom As String
Private _prix As Double
Private _stockactuel As Integer
Private _stockminimum As Integer
' ID do artigo
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
' nome do artigo
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
' preço do artigo
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
' stock atual do artigo
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
' stock mínimo do artigo
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
' fabricante predefinido
Public Sub New()
End Sub
' fabricante com características
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
' método de identificação do artigo
Public Overrides Function ToString() As String
Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
End Function
End Class
End Namespace
Esta classe oferece:
- um construtor que permite definir as 5 informações de um artigo: [id, nom, prix, stockactuel, stockminimum]
- propriedades públicas que permitem ler e escrever as 5 informações.
- uma verificação dos dados introduzidos no artigo. Caso os dados estejam errados, é lançada uma exceção.
- um método toString que permite obter o valor de um artigo sob a forma de uma cadeia de caracteres. Isto é frequentemente útil para a depuração de 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
' lista de todos os artigos
Function getAllArticles() As IList
' adiciona um artigo
Function ajouteArticle(ByVal unArticle As Article) As Integer
' elimina um artigo
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' altera um artigo
Function modifieArticle(ByVal unArticle As Article) As Integer
' procura um artigo
Function getArticleById(ByVal idArticle As Integer) As Article
' elimina todos os artigos
Sub clearAllArticles()
' altera o stock de um artigo
Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
End Interface
End Namespace
A função dos diferentes métodos da interface é a seguinte:
retorna todos os artigos da fonte de dados | |
esvazia a fonte de dados | |
retorna o objeto [Article] identificado pelo seu número | |
permite adicionar um artigo à fonte de dados | |
permite alterar um artigo da fonte de dados | |
permite eliminar um artigo da fonte de dados | |
permite alterar o stock de um artigo da fonte de dados |
A interface disponibiliza aos programas clientes um certo número de métodos definidos apenas pelas suas assinaturas. Não se ocupa da forma como esses métodos serão efetivamente implementados. Isto confere flexibilidade a uma aplicação. O programa cliente efetua as suas chamadas a uma interface e não 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. Sabe-se que, no Windows, praticamente todos os SGBD disponíveis no mercado possuem um controlador ODBC. A vantagem desta solução é que é possível mudar de SGBD de forma transparente para a aplicação. A desvantagem é que um controlador ODBC que apenas explore as características comuns a todos os SGBD tem, em geral, um desempenho inferior ao de um controlador especificamente concebido para explorar todo o potencial de um SGBD específico. Pode consultar-se o parágrafo 3.7 para ver um exemplo de criação de código-fonte para o ODBC.
2.3.1. O código
2.3.1.1. A estrutura
A classe [ArticlesDaoPlainODBC] implementa a interface [IArticlesDao] da seguinte forma:
Comentários:
- na linha 3, importa-se o espaço de nomes que contém as classes .NET de acesso às fontes ODBC
- linha 11 — guardará 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 diferentes métodos da classe
- linhas 22-27 - o construtor. Recebe os elementos que lhe permitem construir o objeto [OdbcConnection] que irá ligar o código à fonte de dados ODBC
- linhas 29-31 — o método para adicionar um artigo
- linhas 33-35 — o método para alterar o stock de um artigo
- linhas 37-39 - o método que elimina todos os artigos da fonte de dados ODBC
- linhas 41-43 - o método que obtém a lista de todos os artigos da fonte ODBC
- linhas 45-47 - o método que permite obter um artigo específico
- linhas 49-51 - o método que permite alterar determinados campos de um artigo cujo número se conhece
- linhas 53-55 - o método que permite eliminar um artigo cujo número se conhece
- linhas 57-60 - método utilitário que permite executar um [SELECT] na fonte de dados e apresentar o resultado
- linhas 62-64 - método utilitário que permite executar um [INSERT, UPDATE, DELETE] na fonte de dados e apresentar o resultado
2.3.1.2. O construtor
Comentários:
- linha 2 — o construtor recebe as três informações de que necessita para se ligar a uma fonte ODBC: o nome DSN da fonte, a identidade com a qual se deve ligar e a palavra-passe associada.
- linha 8 — guarda-se o nome DSN da fonte para poder apresentá-lo nas mensagens de erro.
- linha 9 - o objeto [OdbcConnection] é instanciado. Uma ligação instanciada não é uma ligação aberta. É o método [open] que efetua a abertura.
- linhas 12-19 — preparamos as consultas SQL nos objetos [OdbcCommand]. Isto evitará que tenhamos de as reconstruir sempre que for necessário. Os parâmetros formais ? das consultas serão substituídos por valores reais no momento da execução da consulta.
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, nom, prix, stockactuel, stockminimum from ARTICLES ...] na fonte de dados
- retorna o resultado na forma de 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á encerrada na linha 29, independentemente de ter ocorrido ou não um erro.
- linha 9 - o objeto [OdbcDataReader], necessário para processar o resultado do [Select], é instanciado
- linhas 13-23 - cada linha de resultado do [Select] é colocada num objeto [Article], que se juntará aos outros artigos num objeto [ArrayList]
- a lista de artigos é devolvida na linha 25
- Não é tratada nenhuma exceção. Esta deverá ser tratada 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 solicitação SQL do tipo [Insert, Update, Delete].
- A ligação é aberta na linha 5. Será encerrada na linha 10, independentemente de ter ocorrido ou não uma exceção.
- A consulta de atualização é executada na linha 7. O resultado é devolvido imediatamente; trata-se do número de linhas da tabela ARTICLES alteradas pela consulta.
2.3.1.5. O método ajouteArticle
Comentários:
- linha 1 — o método recebe o artigo a adicionar à fonte de dados ODBC. Devolve o número de linhas afetadas por esta operação, c.a.d. 1 ou 0
- linhas 3 e 20 — o método está sincronizado. Este será o caso de todos os métodos de acesso aos dados. Isto implica que apenas um thread de cada vez poderá trabalhar na fonte de dados. Provavelmente, isto é demasiado conservador. Existem alternativas melhores, nomeadamente a de incluir estas operações em transações. Neste caso, é o SGBD que gere os acessos concorrentes. Não quisemos introduzir o conceito de transação neste momento. O Spring oferece-nos a possibilidade de as introduzir na camada [domain]. Talvez tenhamos oportunidade de voltar a este assunto noutro artigo.
- Nas linhas 5-12, atribuem-se valores aos parâmetros formais da requisição do objeto [insertCommand], inicializado pelo construtor. Recorde-se que esta é:
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.
- Nas linhas 13-19, a consulta é executada. Se tudo correr bem, 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 artigo a modificar na fonte de dados ODBC. Devolve o número de linhas afetadas por esta operação, c.a.d. 1 ou 0
- os comentários do método [ajouteArticle] podem ser reproduzidos aqui
2.3.1.7. O método supprimeArticle
Comentários:
- linha 1 — o método recebe o número do artigo a eliminar na fonte de dados ODBC. Devolve o número de linhas afetadas por esta operação, c.a.d. 1 ou 0
- os comentários do método [ajouteArticle] podem ser reproduzidos aqui
2.3.1.8. O método getAllArticles
Comentários:
- linha 1 — o método não recebe nenhum parâmetro. Devolve a lista de todos os artigos da fonte de dados ODBC
- a consulta [Select], que solicita todos os artigos, é enviada ao método [executeQuery] — linha 6
- a lista obtida é devolvida na linha 8
- nas linhas 9 a 12, trata-se uma eventual exceção
2.3.1.9. O método getArticleById
Comentários:
- linha 1 — o método recebe como parâmetro o número do artigo pretendido. Se este for encontrado na fonte ODBC, o método devolve-o; caso contrário, devolve a referência [nothing].
- A consulta [Select] que solicita o artigo é inicializada nas linhas 5-8
- é executada na linha 12 — obtém-se uma lista de artigos
- se essa lista estiver vazia, é devolvida a referência [nothing] na linha 14
- caso contrário, o único artigo da lista é devolvido na linha 16
- nas linhas 17 a 20, trata-se uma eventual exceção
2.3.1.10. O método clearAllArticles
Comentários:
- linha 1 — o método não recebe nenhum parâmetro e não devolve nada
- linha 6 — a solicitação de eliminação de todos os artigos é executada
- linhas 7-10: trata-se de uma eventual exceção
2.3.1.11. O método changerStockArticle
Comentários:
- linha 1 - o método recebe como parâmetros o número do artigo cujo stock deve ser alterado, bem como o incremento do mesmo (positivo ou negativo). Devolve o número de linhas alteradas pela operação c.a.d. 0 ou 1.
- linhas 5-10: a consulta [updateStockCommand] é inicializada. Recorde-se o texto da consulta SQL:
updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
Note-se que o stock só é alterado se, após a alteração, permanecer >=0.
- A consulta de atualização do stock do artigo é executada na linha 13 e o resultado é apresentado
- nas linhas 14-18, trata-se de uma eventual exceção
2.3.2. Geração do assembly da camada [dao]
O projeto do Visual Studio desta nova versão da camada [dao] tem a seguinte estrutura:

O projeto está configurado para gerar um DLL denominado [webarticles-dao.dll]:
![]() | ![]() |
2.3.3. Testes NUnit da camada [dao]
2.3.3.1. Criação de uma fonte ODBC-Firebird « »
Para testar a nossa nova camada [dao], precisamos de uma fonte de dados ODBC e, portanto, de uma base de dados. Utilizamos o Firebird SGBD (parágrafo 3.5). Com o IBExpert (parágrafo 3.6), criamos a seguinte base de artigos:
![]() | ![]() |
O administrador desta base de dados será o utilizador [SYSDBA] com a palavra-passe [masterkey]. Criamos alguns artigos:

Criamos agora a seguinte fonte Firebird ODBC (ver parágrafo 3.7):
![]() |
A fonte ODBC criada tem as seguintes características:
- nome DSN: odbc-firebird-artigos
- identidade de ligação: SYSDBA
- palavra-passe associada: masterkey
2.3.3.2. A classe de teste de e NUnit
Já escrevemos uma classe de teste para a camada [dao] inicialmente criada. Se o leitor se lembra, essa 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
' o objeto a testar
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' recupera-se uma instância do criador de objetos Spring
Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
' solicita-se a instanciação do objeto articlesdao
articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
End Sub
....
Vê-se que, no método de atributo <Setup()>, é solicitada ao Spring uma referência ao singleton denominado [articlesdao], do tipo [IArticlesDao], ou seja, do tipo da 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 necessidade de alterações nem recompilação.
- Criemos, na pasta do Visual Studio da nossa nova camada [dao], a pasta [tests] (à direita, abaixo), copiando a pasta [bin] do projeto de testes da camada inicial [dao] (à 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], substituímos a DLL e a [webarticles-dao.dll], provenientes daantiga camada [dao] pela DLL e [webarticles-dao.dll], provenientes da nova camada [dao]
- Alteremos 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 com a qual se efetuará o acesso à base de dados - linha 11
- a palavra-passe associada a essa identidade — linha 14
Retomamos aqui as informações da fonte ODBC-Firebird que criámos anteriormente.
2.3.3.3. Testes
Estamos agora prontos para os testes. Com a ajuda da aplicação [Nunit-Gui], carregamos o ficheiro DLL [test-webarticles-dao.dll] da pasta [tests] acima referida e executamos o teste [testGetAllArticles]:

Ao observar a captura de ecrã acima, podemos lamentar o nome [NUnitTestArticlesDaoArrayList] inicialmente atribuído à classe de teste. Isso presta-se a confusão. É, de facto, a classe [ArticlesDaoPlainODBC] que está aqui a ser testada. A captura de ecrã mostra que recuperámos corretamente os artigos que tínhamos colocado na tabela [ARTICLES]. Agora, vamos realizar todos os testes:

Na janela da esquerda, vê-se a lista dos métodos testados. A cor do ponto que precede o nome de cada método indica se o método foi bem-sucedido (verde) ou falhou (vermelho). O leitor que estiver a visualizar este documento no ecrã poderá ver que todos os testes foram bem-sucedidos.
2.3.3.4. Conclusão
Acabámos de demonstrar que:
- porque a classe de teste NUnit fazia referência não a uma classe, mas a uma interface;
- porque o nome exato da classe de instanciação da interface era fornecido num ficheiro de configuração e não no código;
- porque o Spring encarregava-se de instanciar a classe e de fornecer uma referência à mesma ao código de teste;
portanto, o código de teste escrito para a camada inicial [dao] continuava válido para uma nova implementação dessa mesma camada. Não precisámos de ter acesso ao código da classe de teste. Utilizámos apenas a sua versão compilada, a que foi gerada durante o teste da camada inicial [dao]. Chegaremos a conclusões semelhantes quando for necessário 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. Os testes de integração
Recorde-se que a versão inicial da aplicação [webarticles] tinha sido implementada na pasta [runtime] seguinte:
![]() | ![]() |
![]() |
Sugere-se ao leitor que, se necessário, consulte novamente o parágrafo 2.2.4, que detalha as modalidades de implementação da aplicação [webarticles]. Introduzimos as seguintes alterações ao conteúdo da pasta [runtime]:
- na pasta [bin], o ficheiro DLL da antiga camada [dao] é substituído pelo ficheiro DLL da nova camada [dao]
- no [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 ao singleton [articlesDao] uma instância da nova classe [ArticlesDaoPlainODBC]. Esta é a única alteração. Já nos deparámos com ela durante os testes da nova camada [dao].
Estamos prontos para os testes. Configuramos o servidor web [Cassini] da mesma forma que no parágrafo 2.2.4. Inicializamos a tabela de artigos [Firebird] com os seguintes valores:

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

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

Os artigos [parapluie] e [bottes] foram adquiridos e os respetivos stocks foram reduzidos na quantidade adquirida. O artigo [chapeau] não pôde ser adquirido porque a quantidade solicitada excedia a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.3.4.2. Conclusão
O que fizemos?
- recuperámos a versão de implementação da versão anterior;
- substituímos o DLL da camada [dao] por uma nova versão. Os ficheiros DLL das camadas [web] e [domain] permaneceram inalterados;
- alterámos o ficheiro de configuração [web.config] para que tenha em conta a nova classe de implementação da camada [dao]
Tudo isto é bem organizado e proporciona uma grande facilidade de evolução à aplicação web. Estas características importantes são-nos proporcionadas por duas opções de arquitetura:
- o acesso às camadas através de interfaces
- a integração e a configuração das camadas pelo Spring.
Propomos agora 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 disponibiliza um SGBD denominado MSDE, que é uma versão limitada do SQL Server. No anexo, no parágrafo 3.12, é explicado como o obter e instalar.
2.4.1. O código
A classe [ArticlesDaoSqlServer] é muito semelhante à classe [ArticlesDaoPlainODBC] analisada anteriormente. Por isso, iremos indicar apenas as alterações introduzidas em relação à versão anterior:
- as classes necessárias encontram-se no espaço de nomes [System.Data.SqlClient], em vez do espaço de nomes [System.Data.Odbc]
- a ligação do tipo [OdbcConnection] tem agora o tipo [SqlConnection]
- os objetos [OdbcCommand] têm agora o tipo [SqlCommand]
- a sintaxe das consultas SQL configuradas altera-se. A consulta de inserção passa a ser:
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 [ajouteArticle] passa então a ser o seguinte:
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' secção exclusiva
SyncLock Me
' prepara-se a consulta de inserção
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
'executa-se a consulta
Return executeUpdate(insertCommand)
Catch ex As Exception
'erro na consulta
Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
End Try
End SyncLock
End Function
- o fabricante também é alterado:
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' servidor: nome da instância SQL a contactar
' databaseName: nome da base de dados a aceder
' uid: identidade do utilizador
' password: a sua palavra-passe
'recupera-se o nome da base de dados passado como argumento
Me.databaseName = databaseName
': instanciamos a ligação
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' preparam-se as consultas SQL
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:
' servidor: nome da instância SQL a contactar
' databaseName: nome da base de dados a aceder
' uid: identificação do utilizador
' password: a sua palavra-passe
O código completo da 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
' campos privados
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
' construtor
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' servidor: nome da instância SQL a aceder
' databaseName: nome da base de dados a aceder
' uid: identidade do utilizador
' password: a sua palavra-passe
'recupera-se o nome da base de dados passado como argumento
Me.databaseName = databaseName
': instanciamos a ligação
Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
connexion = New SqlConnection(connectString)
' preparam-se as consultas SQL
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
updatecommand = New SqlCommand("update ARTICLES set nom=@nom, prix=@prix, stockactuel=@sa, stockminimum=@sm where id=@id", connexion)
deleteSomeCommand = New SqlCommand("delete from ARTICLES where id=@id", connexion)
selectSomeCommand = New SqlCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=@id", connexion)
updateStockCommand = New SqlCommand("update ARTICLES set stockactuel=stockactuel+@mvt where id=@id and (stockactuel+@mvt)>=0", connexion)
selectAllCommand = New SqlCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
deleteAllCommand = New SqlCommand("delete from ARTICLES", connexion)
End Sub
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' secção exclusiva
SyncLock Me
' prepara-se a consulta de inserção
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
'é executada
Return executeUpdate(insertCommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta de atualização do stock
With updateStockCommand.Parameters
.Clear()
.Add(New SqlParameter("@mvt", mouvement))
.Add(New SqlParameter("@id", idArticle))
End With
'a executar
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
Try
'a consulta de inserção está a ser executada
executeUpdate(deleteAllCommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
Try
'a consulta SELECT está a ser executada
Dim articles As IList = executeQuery(selectAllCommand)
'a lista é devolvida
Return articles
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta SELECT
With selectSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'executa-se
Try
'a consulta está a ser executada
Dim articles As IList = executeQuery(selectSomeCommand)
'verifica-se se o artigo foi encontrado
If articles.Count = 0 Then Return Nothing
'devolve-se o artigo
Return CType(articles.Item(0), Article)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta de atualização
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
' executa-se a consulta
Try
'a consulta de inserção está a ser executada
Return executeUpdate(updatecommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta de eliminação
With deleteSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'está a ser executada
Try
': execução da consulta de eliminação
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'erro na consulta
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
' execução de uma consulta SELECT
' declaração do objeto que permite o acesso a todas as linhas da tabela de resultados
Dim myReader As SqlDataReader = Nothing
Try
'é criada uma ligação à BDD
connexion.Open()
': execução da consulta
myReader = query.ExecuteReader()
'declara-se uma lista de artigos para a devolver posteriormente
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'prepara-se um artigo com os valores do leitor
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)
': adiciona-se o artigo à lista
articles.Add(unArticle)
End While
'devolve-se o resultado
Return articles
Finally
' libertação dos recursos
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
' execução de um pedido de atualização
Try
'cria-se uma ligação à BDD
connexion.Open()
'execução da consulta
Return updateCommand.ExecuteNonQuery()
Finally
' libertação de recursos
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Sugere-se ao leitor que interprete este código à luz dos comentários sobre a classe [ArticlesDaoPlainODBC] apresentados anteriormente.
2.4.2. Geração do assembly da camada [dao]
O novo projeto do Visual Studio tem a seguinte estrutura:

O projeto está configurado para gerar um DLL denominado [webarticles-dao.dll]:
![]() | ![]() |
2.4.3. Testes NUnit da camada [dao]
2.4.3.1. Criação de um servidor de dados de origem SQL
Para testar a nossa nova camada [dao], precisamos de uma fonte de dados SQL Server e, por conseguinte, do SGBD SQL Server. Na verdade, iremos utilizar o SGBD MSDE (MicroSoft Data Engine) (parágrafo 3.12), que é uma versão do SQL Server simplesmente limitada pelo número de utilizadores simultâneos aceites. Com o [EMS MS SQL Manager] (parágrafo 3.14), criamos a seguinte base de artigos numa instância do MSDE denominada [portable1_tahe\msde140405]:
![]() | ![]() |

A base de dados pertence ao utilizador [mdparticles], cuja 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 artigos:

2.4.3.2. A classe de teste NUnit
A classe de teste Nunit da classe de implementação [ArticlesDaoSqlServer] é a mesma que a da classe [ArticlesDaoPlainODBC] (ver parágrafo 2.3.3.2). Seguimos um procedimento semelhante para preparar o teste Nunit da classe:
- criamos, na pasta do Visual Studio do projeto [dao-sqlserver], a pasta [tests] (à direita), copiando a pasta [tests] do projeto [dao-odbc] (à esquerda):
![]() | ![]() |
- na pasta [tests] do projeto [dao-sqlserver], substituímos o DLL [webarticles-dao.dll] pelo DLL [webarticles-dao.dll] resultante da geração do projeto [dao-sqlserver]
- alteramos 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 [ ArticlesDaoSqlServeur]
- esta classe tem um construtor com quatro argumentos:
- o nome da instância MSDE utilizada — linha 9
- o nome da base de dados — linha 12
- a identidade com a qual se efetuará o acesso à base de dados — linha 15
- a palavra-passe associada a essa identidade — linha 18
Retomamos aqui as informações da fonte MSDE que criámos anteriormente.
2.4.3.3. Testes
Estamos prontos para os testes. Com a ajuda da aplicação [Nunit-Gui], carregamos o ficheiro DLL [test-webarticles-dao.dll] da pasta [tests] acima referida e executamos o teste [testGetAllArticles]:

Apesar do nome [NUnitTestArticlesDaoArrayList] atribuído inicialmente à classe de teste — e que foi mantido, uma vez que utilizamos os DLL e [tests-webarticles-dao.dll] provenientes dessa classe —, é efetivamente a classe [ArticlesDaoSqlserver] que está aqui a ser testada. A captura de ecrã mostra que recuperámos corretamente os artigos que tínhamos colocado na tabela [ARTICLES]. Agora, vamos realizar todos os testes:

Na janela da esquerda, vemos a lista dos métodos testados. A cor do ponto que precede o nome de cada método indica se o método foi bem-sucedido (verde) ou falhou (vermelho). O leitor que visualizar este documento no ecrã poderá ver que todos os testes foram bem-sucedidos.
2.4.4. Integração da nova camada [dao] na aplicação [webarticles]
Seguimos o procedimento explicado no parágrafo 2.3.4. Efetua-se as seguintes alterações ao conteúdo da pasta [runtime]:
- na pasta [bin], a DLL daantiga 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 ao singleton [articlesDao] uma instância da nova classe [ArticlesDaoSqlServer]. Esta é a única alteração. Já nos deparámos com ela durante os testes da nova camada [dao]
Estamos prontos para os testes. Mantemos a mesma configuração do servidor web [Cassini] que tínhamos anteriormente. Inicializamos a tabela de artigos [MSDE] com os seguintes valores:

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

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

Os artigos [ballon foot] e [raquette tennis] foram adquiridos e os respetivos stocks foram reduzidos na quantidade adquirida. O artigo [rollers] não pôde ser adquirido porque a quantidade solicitada excedia a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.4.5. A classe de implementação [ArticlesDaoOleDb]
2.4.5.1. As fontes 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 das fontes OleDb é análogo ao das fontes ODBC. Um programa que utiliza uma fonte OleDb fá-lo através de uma interface padrão comum a todas as fontes OleDb. Mudar de fonte OleDb resume-se a mudar de controlador OleDb. O código, por sua vez, não é alterado.
É possível saber quais os controladores OleDb disponíveis no seu computador através do Visual Studio:
- abrir o explorador de servidores através de [Affichage/Explorateur de serveurs]:

- para adicionar uma nova ligação, clique com o botão direito do rato em [Connexion de données] e selecione a opção [Ajouter une connexion]. Aparece então um assistente com o qual é possível definir as características da ligação:

- O painel [Fournisseur] apresenta a lista de controladores OLEDB disponíveis. Para a nova camada [dao], vamos utilizar um controlador [Microsoft Jet 4.0 OLE DB Provider] que dá acesso às bases de dados ACCESS.
- Saímos momentaneamente do Visual Studio para criar a base de dados ACCESS [articles.mdb], que contém a seguinte tabela única:

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

- Escolhemos o controlador [Microsoft Jet 4.0] e passamos para o painel [Connexion]:

- Com o botão [1], selecione a base de dados ACCESS que acabou de ser criada e, em seguida, conclua a definição da ligação com o botão [Terminer]. A ligação criada aparece agora na lista de ligações disponíveis:

- Um duplo clique na tabela [ARTICLES] dá-nos acesso ao seu conteúdo:

- É então possível adicionar, alterar ou eliminar linhas na tabela.
- Selecione, no explorador de servidores, a nova ligação para aceder à sua ficha de propriedades:

- É útil conhecer a cadeia de ligação. Servirá 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, apenas retemos os seguintes elementos:
2.4.5.2. O código da classe [ArticlesDaoOleDb]
A classe [ArticlesDaoOleDb] é muito semelhante à classe [ArticlesDaoPlainODBC] analisada anteriormente. Por isso, indicaremos apenas as alterações introduzidas em relação à versão anterior:
- as classes necessárias encontram-se no espaço de nomes [System.Data.OleDb], em vez do espaço de nomes [System.Data.Odbc]
- a ligação do tipo [OdbcConnection] tem agora o tipo [OleDbConnection]
- os objetos [OdbcCommand] têm agora o tipo [OleDbCommand]
O construtor da classe aceita como único parâmetro a cadeia de ligação à base de dados:
' construtor
Public Sub New(ByVal connectString As String)
' connectString: cadeia de ligação à fonte OleDb
'instanciar a ligação
connexion = New OleDbConnection(connectString)
' preparam-se as consultas SQL
...
End Sub
O código completo da 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
' campos privados
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
' construtor
Public Sub New(ByVal connectString As String)
' connectString: cadeia de ligação à fonte OleDb
'instanciamos a ligação
connexion = New OleDbConnection(connectString)
' preparam-se as consultas SQL
insertCommand = New OleDbCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
updatecommand = New OleDbCommand("update ARTICLES set nom=?, prix=?, stockactuel=?, stockminimum=? where id=?", connexion)
deleteSomeCommand = New OleDbCommand("delete from ARTICLES where id=?", connexion)
selectSomeCommand = New OleDbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=?", connexion)
updateStockCommand = New OleDbCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
selectAllCommand = New OleDbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
deleteAllCommand = New OleDbCommand("delete from ARTICLES", connexion)
End Sub
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' secção exclusiva
SyncLock Me
' prepara-se a consulta de inserção
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
'é executada
Return executeUpdate(insertCommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta de atualização do stock
With updateStockCommand.Parameters
.Clear()
.Add(New OleDbParameter("mvt1", mouvement))
.Add(New OleDbParameter("id", idArticle))
.Add(New OleDbParameter("mvt2", mouvement))
End With
'em execução
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
Try
'a consulta de inserção está a ser executada
executeUpdate(deleteAllCommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
Try
'a consulta SELECT está a ser executada
Dim articles As IList = executeQuery(selectAllCommand)
'a lista é devolvida
Return articles
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta SELECT
With selectSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'executa-se
Try
': executa-se a consulta
Dim articles As IList = executeQuery(selectSomeCommand)
'verifica-se se o artigo foi encontrado
If articles.Count = 0 Then Return Nothing
'devolve-se o artigo
Return CType(articles.Item(0), Article)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta de atualização
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
' executa-se a consulta
Try
'a consulta de inserção está a ser executada
Return executeUpdate(updatecommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta de eliminação
With deleteSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'está a ser executada
Try
': execução da consulta de eliminação
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'erro na consulta
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
' execução de uma consulta SELECT
' declaração do objeto que permite o acesso a todas as linhas da tabela de resultados
Dim myReader As OleDbDataReader = Nothing
Try
'é criada uma ligação à BDD
connexion.Open()
': execução da consulta
myReader = query.ExecuteReader()
'declara-se uma lista de artigos para a devolver posteriormente
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'prepara-se um artigo com os valores do leitor
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)
': adiciona-se o artigo à lista
articles.Add(unArticle)
End While
'devolve-se o resultado
Return articles
Finally
' libertação dos recursos
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
' execução de um pedido de atualização
Try
'cria-se uma ligação à BDD
connexion.Open()
'execução da consulta
Return sqlCommand.ExecuteNonQuery()
Finally
' libertação de recursos
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Sugere-se ao leitor que interprete este código à luz dos comentários sobre a classe [ArticlesDaoPlainODBC] apresentados anteriormente.
2.4.5.3. Geração do assembly da camada [dao]
O novo projeto do Visual Studio tem a seguinte estrutura:

O projeto está configurado para gerar um DLL denominado [webarticles-dao.dll]:
![]() | ![]() |
2.4.5.4. Testes NUnit da camada [dao]
2.4.5.4.1. A classe de teste NUnit
A classe de teste NUnit da classe de implementação [ArticlesDaoOleDb] é a mesma que a da classe [ArticlesDaoPlainODBC] (ver parágrafo 2.3.3.2). Seguimos um procedimento semelhante para preparar o teste NUnit da classe:
- criamos, na pasta do Visual Studio do projeto [dao-oledb], a pasta [tests] (à direita), copiando a pasta [tests] do projeto [dao-odbc] (à esquerda):
![]() | ![]() |
- na pasta [tests] do projeto [dao-oledb], substituímos a pasta DLL [webarticles-dao.dll] pela pasta DLL [webarticles-dao.dll], resultante da geração do projeto [dao-oledb]
- alteramos 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. Tests
Estamos prontos para os testes. Com a ajuda da aplicação [Nunit-Gui], carregamos o DLL [test-webarticles-dao.dll] da pasta [tests] acima e executamos o teste [testGetAllArticles]:

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

O leitor que visualizar este documento no ecrã poderá ver que todos os testes foram bem-sucedidos (cor verde).
2.4.5.5. Integração da nova camada [dao] na aplicação [webarticles]
Seguimos o procedimento explicado no parágrafo 2.3.4. Efetua-se as seguintes alterações ao conteúdo da pasta [runtime]:
- no conjunto de dados [bin], o DLL daantiga 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 ao singleton [articlesDao] uma instância da nova classe [ArticlesDaoOleDb]. Esta é a única alteração.
Mantemos a mesma configuração do servidor web [Cassini] que tínhamos anteriormente. Inicializamos a tabela de artigos 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. Através de um navegador, acedemos ao URL [http://localhost/webarticles/main.aspx]:

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

Os artigos [pantalon] e [jupe] foram comprados e os respetivos stocks foram reduzidos na quantidade comprada. O artigo [manteau] não pôde ser comprado porque a quantidade solicitada excedia a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.5. A classe de implementação [ArticlesDaoFirebirdProvider]
2.5.1. O provedor de acesso Firebird-net-provider
Já utilizámos uma fonte de dados [Firebird], à qual acedemos através de um controlador ODBC. Embora proporcionem uma grande reutilização ao código que os utiliza, os controladores ODBC têm, no entanto, um desempenho inferior ao dos controladores escritos especificamente para o SGBD em questão. O SGBD [Firebird] pode ser utilizado através de uma biblioteca de classes específicas que pode ser descarregada no 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 link a utilizar para descarregar as classes .NET de acesso ao Firebird SGBD. A instalação do pacote cria uma pasta semelhante à seguinte:

Há dois elementos que nos interessam:
- [FirebirdSql.Data.Firebird.dll]: o assembly que contém as classes .NET de acesso ao Firebird SGBD
- [FirebirdNETProviderSDK.chm]: a documentação sobre estas classes
Posteriormente, para que um projeto do Visual Studio possa utilizar estas classes, faremos duas coisas:
- colocaremos o assembly [FirebirdSql.Data.Firebird.dll] na pasta [bin] do projeto
- vamos adicionar esse mesmo assembly às referências do projeto
2.5.2. O código da classe [ArticlesDaoFirebirdProvider]
A classe [ArticlesDaoFirebirdProvider] é muito semelhante à classe [ArticlesDaoSqlServer] analisada anteriormente. Por isso, indicaremos apenas as alterações introduzidas em relação a essa versão:
- as classes necessárias encontram-se no espaço de nomes [FirebirdSql.Data.Firebird], em vez do espaço de nomes [System.Data.SqlClient]
- a ligação do tipo [SqlConnection] tem agora o tipo [FbConnection]
- os objetos [SqlCommand] têm agora o tipo [FbCommand]
- os objetos [SqlParameter] têm agora o tipo [FbParameter]
O construtor da classe aceita quatro parâmetros, com os quais constrói a cadeia de ligação à base de dados:
' construtor
Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
' servidor: nome da máquina anfitriã do SGBD
' databaseName: caminho de acesso à base de dados
' uid: identidade do utilizador que se liga
' password: a sua palavra-passe
...
End Sub
O código completo da 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
' campos privados
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
' fabricante
Public Sub New(ByVal serveur As String, ByVal databasePath As String, ByVal uid As String, ByVal password As String)
' servidor: nome do computador anfitrião do SGBD Firebird
' databaseName: caminho de acesso à base de dados a utilizar
' uid: identificação do utilizador que se liga à base de dados
' password: a sua palavra-passe
'recupera-se o nome da base de dados passado como argumento
Me.databasePath = databasePath
': instanciamos a ligação
Dim connectString As String = String.Format("DataSource={0};Database={1};User={2};Password={3}", serveur, databasePath, uid, password)
connexion = New FbConnection(connectString)
' preparam-se as consultas SQL
insertCommand = New FbCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
updatecommand = New FbCommand("update ARTICLES set nom=@nom, prix=@prix, stockactuel=@sa, stockminimum=@sm where id=@id", connexion)
deleteSomeCommand = New FbCommand("delete from ARTICLES where id=@id", connexion)
selectSomeCommand = New FbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=@id", connexion)
updateStockCommand = New FbCommand("update ARTICLES set stockactuel=stockactuel+@mvt where id=@id and (stockactuel+@mvt)>=0", connexion)
selectAllCommand = New FbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
deleteAllCommand = New FbCommand("delete from ARTICLES", connexion)
End Sub
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' secção exclusiva
SyncLock Me
' prepara-se a consulta de inserção
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
'é executada
Return executeUpdate(insertCommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta de atualização do stock
With updateStockCommand.Parameters
.Clear()
.Add(New FbParameter("@mvt", mouvement))
.Add(New FbParameter("@id", idArticle))
End With
'em execução
Try
Return executeUpdate(updateStockCommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
Try
'a consulta de inserção está a ser executada
executeUpdate(deleteAllCommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
Try
'a consulta SELECT está a ser executada
Dim articles As IList = executeQuery(selectAllCommand)
'a lista é devolvida
Return articles
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta SELECT
With selectSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'é executada
Try
'a consulta está a ser executada
Dim articles As IList = executeQuery(selectSomeCommand)
'verifica-se se o artigo foi encontrado
If articles.Count = 0 Then Return Nothing
'devolve-se o artigo
Return CType(articles.Item(0), Article)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta de atualização
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
' executa-se a consulta
Try
'a consulta de inserção está a ser executada
Return executeUpdate(updatecommand)
Catch ex As Exception
'erro na consulta
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
' secção exclusiva
SyncLock Me
' prepara-se a consulta de eliminação
With deleteSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'está a ser executada
Try
': execução da consulta de eliminação
Return executeUpdate(deleteSomeCommand)
Catch ex As Exception
'erro na consulta
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
' execução de uma consulta SELECT
' declaração do objeto que permite o acesso a todas as linhas da tabela de resultados
Dim myReader As FbDataReader = Nothing
Try
'é criada uma ligação à BDD
connexion.Open()
': execução da consulta
myReader = query.ExecuteReader()
'declara-se uma lista de artigos para a devolver posteriormente
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'prepara-se um artigo com os valores do leitor
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)
'adiciona-se o artigo à lista
articles.Add(unArticle)
End While
'devolve-se o resultado
Return articles
Finally
' libertação dos recursos
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
' execução de um pedido de atualização
Try
': é criada uma ligação ao BDD
connexion.Open()
'executa-se a consulta
Return updateCommand.ExecuteNonQuery()
Finally
' libertação dos recursos
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Sugere-se ao leitor que interprete este código à luz dos comentários sobre a classe [ArticlesDaoSqlServer] apresentados anteriormente.
2.5.3. Geração do assembly da camada [dao]
O novo projeto do Visual Studio tem a seguinte estrutura:

Note-se a presença do assembly [FirebirdSql.Data.Firebird.dll] nas referências do projeto. Este DLL foi colocado na pasta [bin] do projeto. O projeto está configurado para gerar um DLL denominado [webarticles-dao.dll]:
![]() | ![]() |
2.5.4. Testes Nunit da camada [dao]
2.5.4.1. A classe de teste NUnit
A classe de teste Nunit da classe de implementação [ArticlesDaoFirebirdProvider] é a mesma que a da classe [ArticlesDaoPlainODBC] (ver parágrafo 2.3.3.2). Seguimos um procedimento semelhante para preparar o teste Nunit da classe [ArticlesDaoFirebirdProvider]:
- criamos, na pasta do Visual Studio do projeto [dao-firebird-provider], a pasta [tests] (à direita), copiando a pasta [bin] do projeto de testes da camada [dao-odbc] (à esquerda):
![]() | ![]() |
- na pasta [tests], substituímos o DLL [webarticles-dao.dll] pelo DLL [webarticles-dao.dll] resultante da geração do projeto [dao-firebird-provider]
- alteramos 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 de acesso à base de dados Firebird — linha 12
- o nome de utilizador que se liga — linha 15
- a sua palavra-passe — linha 18
2.5.4.2. Testes
A tabela [ARTICLES] da fonte de dados é preenchida com os seguintes artigos (utilizar IBExpert):

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

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

O leitor que visualizar este documento no ecrã poderá ver que todos os testes foram bem-sucedidos (cor verde). O que não consegue ver é que os testes decorreram significativamente mais rapidamente do que com a base de artigos acedida através de um controlador ODBC da nossa primeira implementação.
2.5.5. Integração da nova camada [dao] na aplicação [webarticles]
Seguimos o procedimento já explicado por duas vezes, nomeadamente no parágrafo 2.3.4. Efetua-se as seguintes alterações ao conteúdo da pasta [runtime]:
- na pasta [bin], a DLL daantiga camada [dao] é substituída pela DLL da nova camada [dao] implementada pela classe [ArticlesDaoFirebirdProvider]. Colocamos também a DLL necessária para o Firebird [FirebirdSql.Data.Firebird.dll]:

- no [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 ao singleton [articlesDao] uma instância da nova classe [ArticlesDaoFirebirdProvider]. Esta é a única alteração.
Estamos prontos para os testes. Configuramos o servidor web [Cassini] tal como nos testes anteriores. Inicializamos a tabela de artigos com os seguintes valores:

Através de um navegador, acedemos à página URL [http://localhost/webarticles/main.aspx]:

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

Os artigos [crayon bille] e [ramette 50 feuilles] foram comprados e os respetivos stocks foram reduzidos na quantidade comprada. O artigo [stylo plume] não pôde ser comprado porque a quantidade solicitada excedia 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] da nossa aplicação [webarticles]. Em todas as ocasiões, conseguimos integrar a nova camada [dao] na aplicação [webarticles] sem necessidade de recompilar as outras duas camadas, [web] e [domain]. Isto foi conseguido, recorde-se, graças a duas opções de arquitetura:
- o acesso às camadas através de interfaces
- a integração das camadas pelo Spring
Gostaríamos de ir um pouco mais longe. Embora diferentes, as nossas quatro implementações da camada [dao] apresentam semelhanças impressionantes. Depois de escrita a primeira implementação, as outras três foram obtidas praticamente por «copiar e colar» e substituição de certas palavras-chave por outras. A lógica, por sua vez, não foi alterada. Podemos questionar-nos se não seria possível ter uma implementação que nos libertasse dos diferentes modos 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 aos dados que sejam independentes da natureza real da fonte de dados. O acesso aos dados é assegurado através de:
- de ficheiros de configuração nos quais são colocadas as informações que definem a fonte de dados e as operações que se pretende realizar sobre a mesma
- uma biblioteca de classes que se baseia nessas informações para aceder aos dados
A ferramenta Ibatis SqlMap foi inicialmente desenvolvida para a plataforma Java. A sua adaptação para a plataforma .NET é recente e, ao que parece, apresenta 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 interessante apresentar a versão .NET.
2.5.6.2. Onde encontrar o IBATIS SqlMap?
O site principal do Firebird é [http://www.ibatis.com/]. A página de downloads disponibiliza as seguintes ligações:

Escolha o link [Stable Binaries], que o redireciona para [SourceForge.net]. Siga o processo de transferência até ao fim. Obterá 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 ao projeto uma referência a cada um desses ficheiros
2.5.6.3. Os ficheiros de configuração do Ibatis SqlMap
Uma fonte de dados [SqlMap] será definida através dos seguintes ficheiros de configuração:
- providers.config: define as bibliotecas de classes a utilizar para aceder aos dados
- sqlmap.config: define as características da ligação a estabelecer
- ficheiros de mapeamento: definem as operações a realizar nos dados
A lógica destes ficheiros é a seguinte:
- para aceder aos dados, vamos precisar de uma ligação. Para representar essa ligação, já nos deparámos com várias classes: OdbcConnection, SqlConnection, OleDbConnection, FbConnection. Também vamos precisar de um objeto [Command] para enviar pedidos SQL: OdbcCommand, SqlCommand, OleDbCommand, FbCommand. Etc. No ficheiro [providers.config], definimos o conjunto de classes de que necessitamos.
- 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 de tabelas de dados e a classe .NET, cujas instâncias conterão essas linhas
- as operações SQL a executar. Estas são identificadas por um nome. O código .NET executa estas operações através dos seus nomes, o que tem como consequência a eliminação de todo o código SQL do código .NET.
2.5.6.4. Os ficheiros de configuração do projeto [dao-sqlmap]
Vamos analisar, através de um exemplo, a natureza exata dos ficheiros de configuração do SqlMap. Vamos considerar o caso em que a fonte de dados é a fonte Firebird ODBC do parágrafo 2.3.3.1.
2.5.6.4.1. providers.config
O ficheiro [providers.config] para uma fonte ODBC é este:
Comentários:
- Um ficheiro [providers.config] é distribuído com o pacote [SqlMap]. Este ficheiro disponibiliza vários fornecedores de acesso (provider) padrão. O código acima provém diretamente deste ficheiro.
- Um <provider> tem um nome — linha 6 — que pode ser qualquer um
- Um <provider> pode estar ativado ([enabled=true]) ou não ([enabled=false]). Se estiver ativado, o DLL referido na linha 8 deve estar acessível. Um ficheiro [providers.config] pode ter 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 emissão de comandos SQL
- linha 11 - classe a utilizar para gerir os parâmetros de um comando SQL configurado
- linha 12 - classe de enumeração dos tipos de dados possíveis para os campos de uma tabela
- linha 13 - nome da propriedade de um objeto [Parameter] que contém o tipo do valor desse parâmetro
- linha 14 - nome da classe [Adapter] que permite criar objetos [DataSet] a partir da fonte de dados
- linha 15 - nome da classe [CommandBuilder] que, associada a um objeto [Adapter], permite gerar automaticamente as propriedades [InsertCommand, DeleteCommand, UpdateCommand] deste a partir da sua propriedade [SelectCommand]
- linhas 16 a 19 — define-se como são geridos os comandos SQL configurados. Consoante o caso, deve escrever-se, por exemplo:
ou então
No primeiro caso, trata-se de parâmetros posicionais formais. Os valores efetivos destes devem ser fornecidos na ordem dos parâmetros formais. No segundo caso, trata-se de parâmetros nomeados. Atribui-se um valor a um parâmetro deste tipo, especificando o seu nome. A ordem já não tem importância.
- linha 16 — indica-se que as fontes ODBC utilizam parâmetros posicionais
- linhas 17-19 - dizem respeito aos parâmetros nomeados. Aqui, não existem.
Estas informações permitem ao SqlMap saber, por exemplo, que classe deve instanciar para criar uma ligação. Neste caso, 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 indica nenhuma fonte ODBC. É o ficheiro [sqlmap.config] que o faz:
Comentários:
- linha 3 — define-se um ficheiro de propriedades [properties.xml]. Este define pares (chave, valor). As chaves podem ser quaisquer. O valor associado a uma chave C é obtido através da notação ${C} no ficheiro [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 baliza <provider> a utilizar 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 Firebird.
- linhas 4-7 - parâmetros de configuração:
- linha 5 — as consultas SQL serão identificadas por um nome que, por sua vez, pode fazer parte de um espaço de nomes. [useStatementNamespaces="false"] indica que não serão utilizados espaços de nomes.
- linha 6 - SqlMap possui diferentes estratégias de cache para minimizar os acessos à fonte de dados. [cacheModelsEnabled="false"] indica que não será utilizada nenhuma.
- linhas 9-13 - definem-se as características da fonte de dados:
- 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. Neste caso, não o utilizámos, mas mantivemos a linha na mesma, uma vez que constava do ficheiro de distribuição padrão.
- linhas 14-16 - lista dos ficheiros que definem as operações SQL a realizar na fonte de dados.
- linha 15 - define o ficheiro de mapeamento [articles.xml]
2.5.6.4.3. articles.xml
Este ficheiro tem duas funções:
- definir um mapeamento de objetos das tabelas da fonte de dados. Nos casos mais simples, isto equivale a associar uma classe a uma linha de uma tabela.
- definir operações SQL parametrizadas e atribuir-lhes nomes.
Vamos utilizar o seguinte ficheiro [articles.xml]:
Comentários:
- linhas 4-11 — define-se um mapeamento entre uma linha da tabela [ARTICLES] da fonte de dados e a classe [istia.st.articles.dao.Article]. A cada coluna (column) da tabela está associada uma propriedade (property) da classe [Article]. Este mapeamento permite que a [SqlMap] construa o resultado de uma operação SQL SELECT. Cada linha de resultado do SELECT será colocada num objeto [Article] de acordo com as regras do mapeamento.
- linha 5 — o mapeamento é delimitado por uma baliza <resultMap> e é nomeado através do atributo [id="article"]. A classe associada é designada pelo atributo [class="istia.st.articles.dao.Article"].
- linhas 14-44 - definem-se as operações SQL necessárias
- linhas 16-18 - define-se uma operação SELECT denominada [getAllArticles]
- linha 16 — a operação SELECT é denominada [name= "getAllArticles "] e o mapeamento a utilizar é definido pelo atributo [resultMap="article"]. Faz-se, portanto, referência aqui ao mapeamento das linhas 5-11
- linha 17 - texto do comando SQL a executar
- linhas 20-22 - define-se o comando SQL-Delete [clearAllArticles] destinado a esvaziar a tabela de artigos.
- linhas 24-27 - define-se o comando SQL-Insert [insertArticle] destinado a adicionar um novo artigo à tabela de artigos. Trata-se de uma consulta parametrizada pelos elementos (#id#, #nome#, #preço#, #stockatual#, #stockmínimo#). Os valores destes 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, nome, preço, stockatual, stockmínimo) referenciadas pelo comando SQL configurado.
- linhas 29-31 - define-se 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"]. Trata-se de uma regra geral. Quando o parâmetro é único, é referenciado pela palavra-chave #value# no texto do comando SQL.
- linhas 33-35 - define-se o comando SQL-Update [modifyArticle] destinado a alterar 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 - define-se o comando SQL-Select [getArticleById], que permite obter a linha de um artigo cujo número é conhecido.
- linhas 41-43 - define-se o comando SQL-Update [changerStockArticle], que altera o campo [stockactuel] de um artigo cujo número é conhecido. As duas informações necessárias, o n.º #id# do artigo e o incremento #mouvement# do stock, serão encontradas num dicionário: [parameterClass="Hashtable"]. Este deverá ter duas chaves: id e mouvement. Serão os valores associados a estas duas chaves que serão utilizados no comando SQL.
2.5.6.4.4. Localização dos ficheiros de configuração
Vamos analisar duas situações diferentes:
- no caso de um teste Nunit, os ficheiros de configuração de [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. O API do SqlMap
As classes do SqlMap estão contidas no DLL, que é normalmente colocado na pasta [bin] da aplicação:

As aplicações que utilizam as classes de SqlMap devem importar o espaço de nomes [IBatisNet.DataMapper]:
Todas as operações SQL são realizadas através de um singleton do tipo [Mapper], uma classe do espaço de nomes [IBatisNet.DataMapper ]. O singleton é obtido da seguinte forma:
Para executar o comando SqlMap [getAllArticles], escreve-se:
- o método [QueryForList] permite obter o resultado de um comando SELECT numa lista
- o primeiro parâmetro é o nome do comando SQL a executar (ver articles.xml)
- o segundo parâmetro é o parâmetro a transmitir à solicitação SQL. Deve corresponder ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass=Nothing]. Por isso, passa-se aqui um ponteiro nulo.
- O resultado é do tipo IList. Os objetos desta lista são indicados pelo atributo [resultMap] do comando SQL-select: [resultMap="article"]. «artigo» é um nome de mapeamento:
A classe associada a este mapeamento é [istia.st.articles.dao.Article]. Por fim, a variável [articles] definida anteriormente é uma lista de objetos [ istia.st.articles.dao.Article]. Assim, obtivemos a totalidade da tabela [ARTICLES] numa única instrução. Se a tabela [ARTICLES] estiver vazia, obtém-se um objeto [IList] com 0 elemento.
Para executar o comando SqlMap [getArticleById], escrever-se-á:
- O método [QueryForObject] permite obter o resultado de um comando SELECT que devolve apenas uma linha
- o primeiro parâmetro é o nome do comando SqlMap a executar
- o segundo parâmetro é o parâmetro a transmitir à solicitação SQL. Deve corresponder ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass="int"]. Por isso, passa-se aqui um inteiro que representa o número do artigo procurado.
- O resultado é do tipo Object. Se o SELECT não tiver devolvido nenhuma linha, obtém-se o ponteiro nulo (nothing) como resultado.
Para executar o comando SqlMap [insertArticle], escrever-se-á:
- o método [Insert] permite executar os comandos SQL e INSERT
- o primeiro parâmetro é o nome do comando SqlMap a executar
- o segundo parâmetro é o parâmetro a transmitir ao comando. Deve corresponder ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass="istia.st.articles.dao.Article"]. Por isso, aqui passa-se um objeto do tipo [istia.st.articles.dao.Article].
Para executar o comando SqlMap [deleteArticle], escrever-se-á:
- o método [Delete] permite executar os comandos SQL e DELETE
- o primeiro parâmetro é o nome do comando SQL a executar
- o segundo parâmetro é o parâmetro a transmitir ao comando. Deve corresponder ao atributo [parameterClass] do comando SqlMap. No [articles.xml], temos o [parameterClass="int"]. Por isso, aqui passa-se o n.º do artigo a eliminar.
- O resultado do método [Delete] é o número de linhas eliminadas
De forma análoga, para executar o comando SqlMap [clearAllArticles], escrever-se-á:
Para executar o comando SqlMap [modifyArticle], deve escrever-se:
- O método [Update] permite executar os comandos SQL e UPDATE
- o primeiro parâmetro é o nome do comando SqlMap a executar
- o segundo parâmetro é o parâmetro a transmitir ao comando. Deve corresponder ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass="istia.st.articles.dao.Article"]. Por isso, aqui passa-se um objeto do tipo [istia.st.articles.dao.Article].
- O resultado do método [Update] é o número de linhas alteradas.
De forma análoga, para executar o comando SqlMap [changerStockArticle], escrever-se-á:
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' atualização
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, configurado com [changerStockArticle], utiliza os parâmetros [id, mouvement]. Por isso, aqui passa-se um dicionário com estas duas chaves.
2.5.6.6. O código da classe [ArticlesDaoSqlMap]
Após as explicações anteriores, estamos agora aptos a 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
' campos privados
Dim mappeur As SqlMapper = Mapper.Instance
' lista de todos os artigos
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
' adição de um artigo
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
SyncLock Me
Try
' unArticle: artigo a adicionar
' inserção
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
' elimina um artigo
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
SyncLock Me
Try
' id: ID do artigo a eliminar
' eliminação
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
' alterar um artigo
Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
SyncLock Me
Try
' atualização
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
' pesquisa de um artigo
Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
SyncLock Me
Try
' id: ID do artigo pesquisado
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
' eliminação de todos os artigos
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
' alteração do stock de um artigo
Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
SyncLock Me
Try
' id: ID do artigo cujo stock está a ser alterado
' movimento: movimento de stock
Dim paramètres As New Hashtable(2)
paramètres("id") = idArticle
paramètres("mouvement") = mouvement
' atualização
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
Convida-se o leitor a ler este código à luz das explicações fornecidas para o API do SqlMap. É interessante notar que a utilização do [SqlMap] reduziu significativamente a quantidade de código a escrever.
2.5.6.7. Geração do assembly da camada [dao]
O novo projeto do Visual Studio tem a seguinte estrutura:

Deve-se notar a presença dos «assembly» necessários ao SqlMap nas referências do projeto. Estes DLL foram colocados na pasta [bin] do projeto. O projeto está configurado para gerar um DLL denominado [webarticles-dao.dll]:
![]() | ![]() |
2.5.6.8. Testes Nunit da camada [dao]
2.5.6.8.1. A classe de teste NUnit
A classe de teste Nunit da classe de implementação [ArticlesDaoSqlMap] é a mesma que a da classe [ArticlesDaoPlainODBC] (ver parágrafo 2.3.3.2). Seguimos um procedimento semelhante para preparar o teste Nunit da classe [ArticlesDaoSqlMap]:
- criamos, na pasta do Visual Studio do projeto [dao-sqlmap], a pasta [test1] (à direita), copiando a pasta [tests] do projeto [dao-odbc] (à esquerda):
![]() | ![]() |
- na pasta [tests], substituímos os ficheiros DLL e [webarticles-dao.dll] pelos ficheiros DLL e [webarticles-dao.dll], resultantes da geração do projeto [dao-sqlmap].
- Adicionamos os ficheiros DLL necessários ao SqlMap, bem como os ficheiros de configuração analisados [providers.config, sqlmap.config, properties.xml, articles.xml].
- Alteramos o ficheiro de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoSqlMap]:
Comentários:
- na linha 7, o objeto [articlesdao] está agora associado a uma instância da classe [ArticlesDaoSqlMap]
- esta classe não possui um construtor. Será utilizado o construtor por predefinição.
2.5.6.8.2. Tests
A tabela [ARTICLES] da fonte de dados Firebird é preenchida com os seguintes itens:

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

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

O leitor que visualizar este documento no ecrã poderá ver que alguns testes foram bem-sucedidos (cor verde), mas que outros falharam (cor vermelha). Os testes que falharam são os testes [testArticleAbsent] e [testChangerStockArticle]. Após longas pesquisas, parece que as causas destas falhas são as seguintes:
- no [testArticleAbsent], solicita-se a alteração de um artigo que não existe. Para tal, utiliza-se o método [modifieArticle], que define o número de linhas alteradas como 0 ou 1. Neste caso, deveria ser 0. Em vez disso, ocorre uma exceção do tipo [IBatisNet.Common.Exceptions.ConcurrentException].
- No [changerStockArticle], temos novamente uma operação do tipo [update]. Trata-se de diminuir um stock numa quantidade superior ao próprio stock. Para tal, utiliza-se o método [changerStockArticle], que retorna o número de linhas alteradas, ou seja, 0 ou 1. O comando SQL foi criado para evitar uma atualização (ver comando SQL «changerStockArticle» em articles.xml) que tornaria o stock negativo. Espera-se aqui obter 0 como resultado do método [changerStockArticle]. Mais uma vez, ocorre uma exceção do tipo [IBatisNet.Common.Exceptions.ConcurrentException].
As possíveis fontes de erro são numerosas:
- o código da classe [ArticlesDaoSqlMap] está errado. É possível. No entanto, provém de uma adaptação de uma classe Java que tinha funcionado corretamente com a versão Java de SqlMap.
- a versão .NET do SqlMap contém erros
- O controlador ODBC do Firebird apresenta erros
- ...
Na ausência de certezas, vamos contornar o obstáculo interceptando a famosa exceção [IBatisNet.Common.Exceptions.ConcurrentException]. O novo código da classe [ArticlesDaoSqlMap] passa a ser o seguinte:
As alterações encontram-se nas linhas: 28, 41, 69. Para as operações SQL do tipo [UPDATE, DELETE], se ocorrer uma exceção do tipo [IBatisNet.Common.Exceptions.ConcurrentException], devolvemos 0 como resultado, indicando assim que nenhuma linha foi alterada ou eliminada. Feito isto, a DLL do projeto é regenerada, colocada na pasta [test1] e os testes NUnit são reiniciados:

Desta vez está tudo certo. A partir de agora, vamos trabalhar com este DLL.
2.5.6.9. Integração da nova camada [dao] na aplicação [webarticles]
2.5.6.9.1. fonte de dados ODBC
Testamos aqui a fonte de dados ODBC analisada no parágrafo 2.3.3.1. Esta é aqui utilizada através do SqlMap.
Seguimos o procedimento descrito no parágrafo 2.3.4. Efetua-se as seguintes alterações ao conteúdo da pasta [runtime]:
- na pasta [bin], a DLL daantiga camada [dao] é substituída pela DLL da nova camada [dao] implementada pela classe [ArticlesDaoSqlMap]. Acrescentamos as camadas DLL necessárias para o Firebird e a SqlMap:

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

- no [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:
- na linha 14, associa-se ao singleton [articlesDao] uma instância da nova classe [ArticlesDaoSqlMap]. Esta é a única alteração.
Estamos prontos para os testes. Configuramos o servidor web [Cassini] tal como nos testes anteriores. Inicializamos a tabela de artigos com os seguintes valores:

Através de um navegador, acedemos à página URL [http://localhost/webarticles/main.aspx]:

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

Os artigos [couteau] e [cuiller] foram adquiridos e os respetivos stocks foram reduzidos na quantidade adquirida. O artigo [fourchette] não pôde ser adquirido porque a quantidade solicitada excedia a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.5.6.9.2. fonte de dados MSDE
Testamos aqui a fonte de dados MSDE analisada no parágrafo 2.4.3.1. Esta é aqui utilizada através de SqlMap. Seguimos o mesmo procedimento que anteriormente. Efetua-se as seguintes alterações ao conteúdo do ficheiro [runtime]:
- o conteúdo da pasta [bin] não se altera
- no [runtime], os ficheiros de configuração do SqlMap e do [providers.config, properties.xml] são alterados. Os ficheiros de configuração do [sqlmap.config, articles.xml] não são alterados.
- 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 às fontes de dados do servidor SQL. Está integrado de forma padrão no ficheiro modelo [providers.config] distribuído com o SqlMap.
- O ficheiro [properties.xml] define o <provider> da fonte MSDE, bem como a cadeia de ligação da mesma:
<?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] não sofre alterações.
Estamos prontos para os testes. O servidor web [Cassini] mantém a sua configuração habitual. Inicializamos a tabela de artigos da fonte MSDE com [EMS MS SQL Manager]:

Através de um navegador, acedemos a URL e [http://localhost/webarticles/main.aspx]:

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

Os artigos [ballon foot] e [raquette tennis] foram adquiridos e os respetivos stocks foram reduzidos na quantidade adquirida. O artigo [rollers] não pôde ser adquirido porque a quantidade solicitada excedia a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.5.6.9.3. fonte de dados OleDb
Testamos aqui a fonte de dados ACCESS apresentada no parágrafo 2.4.5.1. Esta é aqui utilizada através de SqlMap. Seguimos o mesmo procedimento que anteriormente. Efetua-se as seguintes alterações ao conteúdo do ficheiro [runtime]:
- o conteúdo da pasta [bin] não se altera
- no [runtime], os ficheiros de configuração do SqlMap e do [providers.config, properties.xml] são alterados. Os ficheiros de configuração do [sqlmap.config, articles.xml] não são alterados.
- 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 às fontes de dados OleDb. Está integrado de forma padrão no ficheiro modelo [providers.config] distribuído com o SqlMap.
- O ficheiro [properties.xml] define o <provider> da fonte OleDb, bem como a cadeia de ligação da mesma:
<?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>
- no [runtime], o ficheiro de configuração [web.config] não sofre alterações.
Estamos prontos para os testes. O servidor web [Cassini] mantém a sua configuração habitual. Inicializamos a tabela de artigos da fonte ACCESS da seguinte forma:

Através de um navegador, acedemos ao URL [http://localhost/webarticles/main.aspx]:

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

Os artigos [pantalon] e [jupe] foram comprados e os respetivos stocks foram reduzidos na quantidade comprada. O artigo [manteau] não pôde ser comprado porque a quantidade solicitada excedia a quantidade em stock. Convidamos o leitor a realizar testes adicionais.
2.5.7. Conclusão
Terminamos 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 as classes de acesso .NET para aceder às fontes ODBC
- utilizando as classes de acesso .NET para as fontes SQL Server
- utilizando as classes de acesso .NET para aceder às fontes OleDb
- utilizando classes de acesso de terceiros para aceder a uma base de dados Firebird
- em cada uma dessas ocasiões, integramos a nova camada [dao] na aplicação [webarticles] de três camadas [web, domain, dao] sem recompilar nenhuma das camadas [web, domain]
- Por fim, apresentámos 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, pudemos utilizar sucessivamente as fontes de dados das implementações 1 a 3 anteriores. Isto foi feito de forma transparente através de ficheiros de configuração.
- Demonstrámos a grande flexibilidade que as ferramentas Spring e SqlMap proporcionavam às aplicações web de três camadas.































































