2. Parte 2
2.1. Introduction
Começaremos relembrando o que foi feito na parte 1 e, em especial, a arquitetura de três camadas [web, domain, dao] utilizada. Na solução proposta, a camada [dao] era uma camada de teste: a fonte dos dados era implementada por um objeto [ArrayList]. Neste artigo, vamos nos concentrar na camada [dao], apresentando diversas implementações possíveis para ela quando os dados estão em um SGBD.
Ferramentas utilizadas:
- o SGBD do Firebird — consulte o anexo, parágrafo 3.5.
- o SGBD MSDE (Microsoft Data Engine) — consulte o anexo, parágrafo 3.12.
- IBExpert, edição pessoal para administrar graficamente o SGBD Firebird — consulte o anexo, parágrafo 3.6.
- EMS MS SQL Manager para administrar graficamente o SGBD MSDE - consulte o anexo, parágrafo 3.14.
- Ibatis SqlMap para a camada de acesso aos dados do SGBD — consulte o parágrafo 2.5.6.2.
Em uma escala que vai de iniciante a intermediário e avançado, este documento se enquadra na parte [intermédiaire-avancé]. Sua compreensão requer vários pré-requisitos. Alguns deles podem ser adquiridos em documentos que escrevi. Nesse caso, eu os cito. É claro que isso é apenas uma sugestão e que o leitor pode utilizar seus documentos favoritos.
- 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 ]
- uso do componente 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 do aplicativo web simplificado de comércio eletrônico estudado na Parte 1. Ele permite que os usuários da web:
- consultar uma lista de artigos provenientes de um banco de dados
- colocar alguns deles em um carrinho virtual
- confirmar o carrinho. Essa confirmação tem como único efeito atualizar, no banco de dados, os estoques dos itens comprados.
2.2.1. As visualizações do aplicativo
As diferentes visualizações apresentadas ao usuário são as seguintes:
![]() |
![]() |
![]() |
- a tela [ERREURS], que sinaliza qualquer erro do aplicativo

2.2.2. Arquitetura geral do aplicativo
A aplicação desenvolvida na Parte 1 possui uma arquitetura de três camadas:
![]() |
- as três camadas foram tornadas independentes graças ao uso de interfaces
- a integração das diferentes camadas foi realizada com o Spring
- cada camada possui namespaces separados: web (camada UI), domain (camada de negócios) e dao (camada de acesso aos dados).
A aplicação segue uma arquitetura MVC (Modelo - Visão - Controlador). Se retomarmos o esquema em camadas acima, a arquitetura MVC se integra a ele da seguinte maneira:
![]() |
O processamento de uma solicitação de um cliente ocorre de acordo com as seguintes etapas:
- o cliente faz uma solicitação ao controlador. Esse controlador é, neste caso, uma página .aspx à qual é atribuída uma função específica. Ela recebe todas as solicitações dos clientes. É a porta de entrada do aplicativo. É o C de MVC.
- o controlador processa essa solicitação. Para isso, ele pode precisar da ajuda da camada de negócios, o que é chamado de modelo M na estrutura MVC.
- O controlador recebe uma resposta da camada de negócios. A solicitação do cliente foi processada. Isso pode resultar em 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 (= visualização) a ser enviada ao cliente. Na maioria das vezes, trata-se de uma página contendo elementos dinâmicos. O controlador fornece esses elementos à visualização.
- A visualização é enviada ao cliente. É o V de MVC.
2.2.3. O modelo
O modelo M do MVC é composto, neste caso, pelos seguintes elementos:
- as classes de negócio
- as classes de acesso aos dados
- o banco de dados
2.2.3.1. O banco de dados
O banco de dados contém apenas uma tabela chamada 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 item de forma exclusiva | |
nome do artigo | |
seu preço | |
estoque atual | |
o nível de estoque abaixo do qual deve ser feito um pedido de reabastecimento |
2.2.3.2. Os espaços de nomes do modelo
O modelo M é fornecido na 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ócios da camada [domain]
Cada um desses espaços de nomes está contido em um arquivo “assembly” próprio:
assembly | contenu | rôle |
webarticles-dao | - [IArticlesDao]: a interface de acesso à camada [dao]. Essa é a única interface que a camada [domain] vê. Ela 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 — está inteiramente na camada [dao] da arquitetura de três camadas do aplicativo web |
webarticles-domain | - [IArticlesDomain]: a interface de acesso à camada [domain]. É a única interface que a camada web vê. Ela 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 de compras na web - está inteiramente na camada [domain] da arquitetura de três camadas do aplicativo web |
2.2.4. Implantação e testes da aplicação [webarticles]
2.2.4.1. Implantação
Implantamos a aplicação desenvolvida na Parte 1 do artigo em uma pasta chamada [runtime]:
![]() | ![]() |
![]() |
Comentários:
A pasta [runtime] contém três arquivos e duas subpastas:
- os controladores [global.asax] e [main.aspx]
- o arquivo de configuração [web.config]
- a pasta [bin], que contém:
- os DLL das três camadas [webarticles-dao.dll], [webarticles-domain.dll] e [webarticles-web.dll]
- os arquivos necessários para o Spring: [Spring-Core.*], [log4net.dll]
- a pasta [vues], que contém o código de apresentação das diferentes visualizações.
- A presença dos arquivos de código .vb é desnecessária, uma vez que sua versão compilada está nos DLL.
2.2.4.2. Testes
Configuramos o servidor web [Cassini] da seguinte maneira:

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

Vale lembrar que a camada [dao] é implementada por uma classe que armazena os artigos em um objeto [ArrayList]. Essa classe cria uma lista inicial de quatro artigos. A partir da visualização acima, utilizamos os links do menu para realizar operações. Aqui estão algumas delas. A coluna da esquerda representa a solicitação do cliente e a coluna da direita, a resposta dada a ele.
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
![]() | ![]() |
2.2.5. A camada [dao] revisitada
Em nossa primeira implementação da camada [dao], a interface [IArticlesDao] de acesso aos dados havia sido implementada por uma classe que armazenava os artigos em um objeto [ArrayList]. Isso nos permitiu não nos aprofundarmos nessa camada e demonstrar que o que importava era apenas sua interface, e não sua implementação. Assim, conseguimos construir um aplicativo web operacional. Esse aplicativo possui três camadas: [web], [domain] e [dao]. Apresentaremos aqui diferentes implementações da camada [dao]. Cada uma delas poderá substituir a camada [dao] atual sem qualquer modificação nas camadas [domain] e [web]. Essa flexibilidade é obtida porque:
- a camada [domain] não se refere a uma classe concreta, mas a uma interface [IArticlesDao]
- graças ao Spring, conseguimos ocultar da camada [domain] o nome da classe de implementação da interface [IArticlesDao].
2.2.5.1. Elementos da camada [dao]
Vamos relembrar 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
' estoque 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
' estoque 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 padrão
Public Sub New()
End Sub
' fabricante com propriedades
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 item
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 gravar as 5 informações.
- uma verificação dos dados inseridos no artigo. Caso haja dados incorretos, é lançada uma exceção.
- um método toString que permite obter o valor de um artigo na forma de uma sequência de caracteres. Isso costuma ser útil para a depuração de um aplicativo.
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 itens
Function getAllArticles() As IList
' adiciona um artigo
Function ajouteArticle(ByVal unArticle As Article) As Integer
' exclui um artigo
Function supprimeArticle(ByVal idArticle As Integer) As Integer
' altera um artigo
Function modifieArticle(ByVal unArticle As Article) As Integer
' pesquisa um artigo
Function getArticleById(ByVal idArticle As Integer) As Article
' exclui todos os itens
Sub clearAllArticles()
' altera o estoque 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 itens da fonte de dados | |
esvazia a fonte de dados | |
retorna o objeto [Article] identificado por seu número | |
permite adicionar um item à fonte de dados | |
permite modificar um item da fonte de dados | |
permite excluir um item da fonte de dados | |
permite modificar o estoque de um item da fonte de dados |
A interface disponibiliza aos programas clientes uma série de métodos definidos apenas por suas assinaturas. Ela não se preocupa com a forma como esses métodos serão efetivamente implementados. Isso proporciona flexibilidade à aplicação. O programa cliente faz suas chamadas por meio de uma interface e não de uma implementação específica dela.
![]() |
A escolha de uma implementação específica é feita por meio de um arquivo de configuração do Spring.
2.3. A classe de implementação [ArticlesDaoPlainODBC]
Oferecemos uma nova implementação da camada [dao], que pressupõe que os dados estejam em uma fonte ODBC. Sabe-se que, no Windows, praticamente todos os SGBD disponíveis no mercado possuem um driver ODBC. A vantagem dessa solução é que é possível trocar de SGBD de forma transparente para o aplicativo. A desvantagem é que um driver ODBC que utilize apenas os recursos comuns a todos os SGBD geralmente apresenta desempenho inferior ao de um driver escrito especificamente para explorar todo o potencial de um SGBD específico. Consulte 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 maneira:
Comentários:
- na linha 3, importa-se o namespace que contém as classes .NET de acesso às fontes ODBC
- linha 11 — armazenará a conexão com a 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. Ele recebe os elementos que lhe permitem construir o objeto [OdbcConnection], que conectará o código à fonte de dados ODBC
- linhas 29-31 — o método para adicionar um item
- linhas 33-35 — o método para alterar o estoque de um item
- linhas 37-39 - o método que exclui todos os itens da fonte de dados ODBC
- linhas 41-43 – o método que obtém a lista de todos os itens da fonte ODBC
- linhas 45-47 — o método que permite obter um artigo específico
- linhas 49-51 — o método que permite modificar determinados campos de um artigo cujo número é conhecido
- linhas 53-55 — o método que permite excluir um item cujo número é conhecido
- linhas 57-60 - método utilitário que permite executar um [SELECT] na fonte de dados e retornar o resultado
- linhas 62-64 - método utilitário que permite executar um [INSERT, UPDATE, DELETE] na fonte de dados e exibir o resultado
2.3.1.2. O construtor
Comentários:
- linha 2 — o construtor recebe as três informações necessárias para se conectar a uma fonte ODBC: o nome DSN da fonte, a identidade com a qual se deve conectar e a senha associada.
- linha 8 — armazena-se o nome DSN da fonte para poder apresentá-lo nas mensagens de erro.
- linha 9 — o objeto [OdbcConnection] é instanciado. Uma conexão instanciada não é uma conexão aberta. É o método [open] que realiza a abertura.
- linhas 12-19 — preparamos as consultas SQL nos objetos [OdbcCommand]. Isso nos evitará ter que recriá-las toda vez 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 conexão é aberta. Ela será fechada na linha 29, independentemente de ter ocorrido um erro ou não.
- linha 9 — o objeto [OdbcDataReader], necessário para processar o resultado do [Select], é instanciado
- linhas 13-23 — cada linha de resultado do [Select] é colocada em um objeto [Article], que se juntará aos outros itens em um objeto [ArrayList]
- a lista de itens é retornada na linha 25
- Nenhuma exceção é tratada. Ela 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 conexão é aberta na linha 5. Ela será fechada na linha 10, independentemente de ter ocorrido uma exceção ou não.
- A consulta de atualização é executada na linha 7. O resultado é retornado imediatamente; trata-se do número de linhas da tabela ARTICLES modificadas pela consulta.
2.3.1.5. O método ajouteArticle
Comentários:
- linha 1 — o método recebe o item a ser adicionado à fonte de dados ODBC. Ele retorna o número de linhas afetadas por essa operação, c.a.d. 1 ou 0
- linhas 3 e 20 — o método é sincronizado. Esse será o caso de todos os métodos de acesso aos dados. Isso significa que apenas um thread por vez poderá trabalhar na fonte de dados. Provavelmente, isso é muito conservador. Existem alternativas melhores, notadamente a de incluir essas operações em transações. Nesse caso, é o SGBD que gerencia os acessos simultâneos. Não quisemos introduzir o conceito de transação neste momento. O Spring nos oferece a possibilidade de introduzi-las na camada [domain]. Talvez tenhamos a oportunidade de voltar a esse assunto em outro artigo.
- Nas linhas 5 a 12, atribuímos valores aos parâmetros formais da consulta do objeto [insertCommand] inicializado pelo construtor. Recorde-se que ela é:
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 a 11.
- Nas linhas 13 a 19, a consulta é executada. Se tudo correr bem, o resultado é retornado. Caso contrário, é lançada uma exceção genérica com uma mensagem de erro explícita
2.3.1.6. O método modifieArticle
Comentários:
- linha 1 — o método recebe o item a ser modificado na fonte de dados ODBC. Ele retorna o número de linhas afetadas por essa 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 item a ser excluído na fonte de dados ODBC. Ele retorna o número de linhas afetadas por essa 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. Ele retorna a lista de todos os itens da fonte de dados ODBC
- a consulta [Select], que solicita todos os itens, é enviada ao método [executeQuery] — linha 6
- a lista obtida é retornada 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 desejado. Ele retorna esse número se ele for encontrado na fonte ODBC; caso contrário, retorna a referência [nothing].
- A consulta [Select] solicitando o artigo é inicializada nas linhas 5 a 8
- ela é executada na linha 12 — obtém-se uma lista de artigos
- se essa lista estiver vazia, é retornada a referência [nothing] na linha 14
- caso contrário, o único item da lista é retornado 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 retorna nada
- linha 6 — a solicitação de exclusão de todos os artigos é executada
- linhas 7 a 10: trata-se de uma possível 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 estoque deve ser alterado, bem como o incremento desse estoque (positivo ou negativo). Ele retorna o número de linhas alteradas pela operação c.a.d. 0 ou 1.
- linhas 5-10: a consulta [updateStockCommand] é inicializada. Vale lembrar o texto da consulta SQL:
updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
Observe-se que o estoque só é alterado se, após a alteração, permanecer >=0.
- A consulta de atualização do estoque do artigo é executada na linha 13 e o resultado é exibido
- nas linhas 14 a 18, trata-se uma possível 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 de dados ODBC-Firebird
Para testar nossa nova camada [dao], precisamos de uma fonte de dados ODBC e, portanto, de um banco de dados. Utilizamos o Firebird SGBD (parágrafo 3.5). Com o IBExpert (parágrafo 3.6), criamos o seguinte banco de dados de artigos:
![]() | ![]() |
O administrador desse banco de dados será o usuário [SYSDBA] com a senha [masterkey]. Criamos alguns artigos:

Agora, criamos a seguinte fonte ODBC do Firebird (ver parágrafo 3.7):
![]() |
A fonte ODBC criada possui as seguintes características:
- nome DSN: odbc-firebird-artigos
- identidade de conexão: SYSDBA
- senha associada: masterkey
2.3.3.2. A classe de teste de , NUnit
Já escrevemos uma classe de teste para a camada [dao] criada inicialmente. 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 ser testado
Private articlesDao As IArticlesDao
<SetUp()> _
Public Sub init()
' recuperando uma instância do gerador 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
....
Percebe-se que, no método de atributo <Setup()>, solicita-se 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 arquivo 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 nossa nova camada [dao] sem modificações nem recompilação.
- Vamos criar, 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, o leitor pode consultar 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]
- vamos modificar o arquivo de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoPlainODBC]:
Comentários:
- na linha 6, o objeto [articlesdao] agora está associado a uma instância da classe [ ArticlesDaoPlainODBC]
- essa classe possui um construtor com três argumentos:
- o nome da fonte DSN — linha 8
- a identidade com a qual será feito o acesso ao banco de dados — linha 11
- a senha associada a essa identidade — linha 14
Retomamos aqui as informações da fonte ODBC-Firebird que criamos anteriormente.
2.3.3.3. Testes
Agora estamos prontos para os testes. Com o auxílio do aplicativo [Nunit-Gui], carregamos o DLL e o [test-webarticles-dao.dll] da pasta [tests] acima e executamos o teste [testGetAllArticles]:

Ao observar a captura de tela acima, podemos lamentar o nome [NUnitTestArticlesDaoArrayList] dado inicialmente à classe de teste. Isso gera confusão. Na verdade, é a classe [ArticlesDaoPlainODBC] que está sendo testada aqui. A captura de tela mostra que recuperamos corretamente os itens que havíamos inserido na tabela [ARTICLES]. Agora, vamos realizar todos os testes:

Na janela à 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 aprovado (verde) ou reprovado (vermelho). O leitor que estiver visualizando este documento na tela poderá ver que todos os testes foram aprovados.
2.3.3.4. Conclusão
Acabamos 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 foi fornecido em um arquivo de configuração e não no código;
- porque o Spring se encarregava de instanciar a classe e fornecer uma referência a ela ao código de teste;
portanto, o código de teste escrito para a camada inicial [dao] permanecia válido para uma nova implementação dessa mesma camada. Não precisamos ter acesso ao código da classe de teste. Utilizamos apenas sua versão compilada, aquela 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. Testes de integração
Vale lembrar que a versão inicial do aplicativo [webarticles] havia sido implantada na pasta [runtime] a seguir:
![]() | ![]() |
![]() |
Recomenda-se ao leitor que, se necessário, consulte o parágrafo 2.2.4, que detalha as modalidades de implantação do aplicativo [webarticles]. Estamos realizando as seguintes alterações no conteúdo da pasta [runtime]:
- na pasta [bin], o arquivo DLL da camada antiga [dao] é substituído pelo arquivo DLL da nova camada [dao]
- no [runtime], o arquivo de configuração [web.config] é substituído por um arquivo que leva em conta a nova classe de implementação da camada [dao]:
![]() |
![]() |
O novo arquivo de configuração [web.config] é o seguinte:
Comentários:
- as linhas 14 a 24 associam ao singleton [articlesDao] uma instância da nova classe [ArticlesDaoPlainODBC]. Essa é a única modificação. Já a encontramos 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 os servidores SGBD e [Firebird], estejam em execução. Com um navegador, acessamos a URL [http://localhost/webarticles/main.aspx]:

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

Os itens [parapluie] e [bottes] foram comprados e seus estoques foram reduzidos pela quantidade comprada. O item [chapeau] não pôde ser comprado porque a quantidade solicitada excedia a quantidade em estoque. Convidamos o leitor a realizar testes adicionais.
2.3.4.2. Conclusão
O que fizemos?
- retomamos a versão de implantação da versão anterior;
- substituímos o DLL da camada [dao] por uma nova versão. Os DLL das camadas [web] e [domain] permaneceram inalterados;
- modificamos o arquivo de configuração [web.config] para que ele considere a nova classe de implementação da camada [dao]
Tudo isso é bem organizado e oferece grande facilidade de evolução para a aplicação web. Essas características importantes nos são proporcionadas por duas escolhas de arquitetura:
- o acesso às camadas por meio 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 estejam em um banco de dados SQL Server. A Microsoft disponibiliza um SGBD chamado MSDE, que é uma versão limitada do SQL Server. No anexo, no parágrafo 3.12, é explicado como obtê-lo e instalá-lo.
2.4.1. O código
A classe [ArticlesDaoSqlServer] é muito semelhante à classe [ArticlesDaoPlainODBC] analisada anteriormente. Portanto, indicaremos apenas as alterações feitas em relação à versão anterior:
- as classes necessárias estão no espaço de nomes [System.Data.SqlClient], em vez do espaço de nomes [System.Data.Odbc]
- a conexão do tipo [OdbcConnection] agora tem o tipo [SqlConnection]
- os objetos [OdbcCommand] agora têm o tipo [SqlCommand]
- a sintaxe das consultas SQL configuradas é alterada. 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 antes era:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
- o método [ajouteArticle] passa a ser o seguinte:
Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
' seçã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 construtor 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 ser acessada
' databaseName: nome do banco de dados a ser acessado
' uid: identidade do usuário
' senha: sua senha
': recupera-se o nome do banco de dados passado como argumento
Me.databaseName = databaseName
': instanciamos a conexã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)
' prepara-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 agora aceita quatro parâmetros:
' servidor: nome da instância SQL a ser acessada
' databaseName: nome do banco de dados a ser acessado
' uid: identificação do usuário
' password: sua senha
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 ser acessada
' databaseName: nome do banco de dados a ser acessado
' uid: identidade do usuário
' senha: sua senha
': recupera-se o nome do banco de dados passado como argumento
Me.databaseName = databaseName
': instanciamos a conexã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)
' prepara-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
' seçã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
' seção exclusiva
SyncLock Me
' preparando a consulta de atualização do estoque
With updateStockCommand.Parameters
.Clear()
.Add(New SqlParameter("@mvt", mouvement))
.Add(New SqlParameter("@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
' seção exclusiva
SyncLock Me
Try
'executando a consulta de inserção
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
' seção exclusiva
SyncLock Me
Try
'executando a consulta SELECT
Dim articles As IList = executeQuery(selectAllCommand)
'retornando a lista
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
' seção exclusiva
SyncLock Me
' prepara-se a consulta SELECT
With selectSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
': a consulta é executada
Try
'executando a consulta
Dim articles As IList = executeQuery(selectSomeCommand)
'verificando se o artigo foi encontrado
If articles.Count = 0 Then Return Nothing
'retornamos 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
' seçã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
' a consulta é executada
Try
'executando a consulta de inserção
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
' seção exclusiva
SyncLock Me
' prepara-se a consulta de exclusão
With deleteSomeCommand.Parameters
.Clear()
.Add(New SqlParameter("@id", idArticle))
End With
'executando
Try
'executando a consulta de exclusã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
': está sendo criada uma conexão com a BDD
connexion.Open()
': execução da consulta
myReader = query.ExecuteReader()
'declara-se uma lista de itens para ser retornada posteriormente
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
': prepara-se um item 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 item à lista
articles.Add(unArticle)
End While
'retorna-se o resultado
Return articles
Finally
' liberaçã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 uma solicitação de atualização
Try
': cria-se uma conexão com o BDD
connexion.Open()
'execução da consulta
Return updateCommand.ExecuteNonQuery()
Finally
' liberação de recursos
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Recomenda-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 fonte de dados SQL
Para testar nossa nova camada [dao], precisamos de uma fonte de dados SQL Server e, portanto, do SGBD SQL Server. Na verdade, utilizaremos o SGBD MSDE (MicroSoft Data Engine) (parágrafo 3.12), que é uma versão do SQL Server simplesmente limitada pelo número de usuários simultâneos aceitos. Com o [EMS MS SQL Manager] (parágrafo 3.14), criamos a seguinte base de artigos em uma instância do MSDE denominada [portable1_tahe\msde140405]:
![]() | ![]() |

A base de dados pertence ao usuário [mdparticles], cuja senha é [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 os arquivos DLL e [webarticles-dao.dll] pelos arquivos DLL e [webarticles-dao.dll] gerados a partir do projeto [dao-sqlserver]
- modificamos o arquivo de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoSqlServer]:
Comentários:
- na linha 7, o objeto [articlesdao] agora está associado a uma instância da classe [ ArticlesDaoSqlServeur]
- essa classe possui um construtor com quatro argumentos:
- o nome da instância MSDE utilizada — linha 9
- o nome do banco de dados — linha 12
- a identidade com a qual será feito o acesso ao banco de dados — linha 15
- a senha associada a essa identidade — linha 18
Aqui, utilizamos as informações da fonte MSDE que criamos anteriormente.
2.4.3.3. Testes
Estamos prontos para os testes. Com o auxílio do aplicativo [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 — e que foi mantido, já que estamos utilizando os arquivos DLL e [tests-webarticles-dao.dll] derivados dessa classe —, é de fato a classe [ArticlesDaoSqlserver] que está sendo testada aqui. A captura de tela mostra que recuperamos corretamente os itens que havíamos inserido na tabela [ARTICLES]. Agora, vamos realizar todos os testes:

Na janela à 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 aprovado (verde) ou reprovado (vermelho). O leitor que estiver visualizando este documento na tela poderá ver que todos os testes foram aprovados.
2.4.4. Integração da nova camada [dao] na aplicação [webarticles]
Seguimos o procedimento explicado no parágrafo 2.3.4. Realizamos as seguintes alterações no 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 arquivo de configuração [web.config] é substituído por um arquivo que leva em conta a nova classe de implementação:
Comentários:
- as linhas 15 a 33 associam ao singleton [articlesDao] uma instância da nova classe [ArticlesDaoSqlServer]. Essa é a única modificação. Já a encontramos durante os testes da nova camada [dao]
Estamos prontos para os testes. Mantemos a mesma configuração do servidor web [Cassini] de antes. 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), estejam em execução. Usando um navegador, acessamos o URL e o [http://localhost/webarticles/main.aspx]:

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

Os itens [ballon foot] e [raquette tennis] foram comprados e seus estoques foram reduzidos pela quantidade comprada. O item [rollers] não pôde ser comprado porque a quantidade solicitada excedia a quantidade em estoque. 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 estejam em um banco de dados acessível por meio de um driver OleDb. O princípio das fontes OleDb é análogo ao das fontes ODBC. Um programa que utiliza uma fonte OleDb faz isso por meio de uma interface padrão comum a todas as fontes OleDb. Mudar de fonte OleDb resume-se a mudar de driver OleDb. O código, por sua vez, não é alterado.
É possível verificar quais drivers OleDb estão disponíveis em sua máquina usando o Visual Studio:
- exiba o explorador de servidores por meio de [Affichage/Explorateur de serveurs]:

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

- O painel [Fournisseur] exibe a lista de drivers OLEDB disponíveis. Para a nova camada [dao], vamos utilizar um driver [Microsoft Jet 4.0 OLE DB Provider] que dá acesso aos bancos de dados ACCESS.
- Vamos sair momentaneamente do Visual Studio para criar a base de dados ACCESS [articles.mdb], que contém a seguinte tabela:

- 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 conexão, conforme explicado anteriormente:

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

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

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

- É possível, então, adicionar, modificar ou excluir linhas da tabela.
- Selecione, no explorador de servidores, a nova conexão para acessar sua folha de propriedades:

- É importante conhecer a string de conexão. Ela nos servirá para nos conectarmos ao banco 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
- Dessa string, vamos reter apenas os seguintes elementos:
2.4.5.2. O código da classe [ArticlesDaoOleDb]
A classe [ArticlesDaoOleDb] é muito semelhante à classe [ArticlesDaoPlainODBC] analisada anteriormente. Portanto, indicaremos apenas as alterações feitas em relação à versão anterior:
- as classes necessárias estão no espaço de nomes [System.Data.OleDb], em vez do espaço de nomes [System.Data.Odbc]
- a conexão do tipo [OdbcConnection] agora tem o tipo [OleDbConnection]
- os objetos [OdbcCommand] agora têm o tipo [OleDbCommand]
O construtor da classe aceita como único parâmetro a string de conexão com o banco de dados:
' construtor
Public Sub New(ByVal connectString As String)
' connectString: cadeia de conexão com a fonte OleDb
'instanciamos a conexão
connexion = New OleDbConnection(connectString)
' prepara-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: string de conexão com a fonte OleDb
'instanciamos a conexão
connexion = New OleDbConnection(connectString)
' prepara-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
' seçã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
' seção exclusiva
SyncLock Me
' preparando a consulta de atualização do estoque
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
' seção exclusiva
SyncLock Me
Try
'executando a consulta de inserção
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
' seção exclusiva
SyncLock Me
Try
'executando a consulta SELECT
Dim articles As IList = executeQuery(selectAllCommand)
'retornando a lista
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
' seção exclusiva
SyncLock Me
' prepara-se a consulta SELECT
With selectSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
': a consulta é executada
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
'retorna 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
' seçã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
' a consulta é executada
Try
'executando a consulta de inserção
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
' seção exclusiva
SyncLock Me
' prepara-se a consulta de exclusão
With deleteSomeCommand.Parameters
.Clear()
.Add(New OleDbParameter("id", idArticle))
End With
'é executada
Try
'executando a consulta de exclusã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 conexão com a BDD
connexion.Open()
': execução da consulta
myReader = query.ExecuteReader()
'declara-se uma lista de itens para ser retornada posteriormente
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
': prepara-se um item 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 item à lista
articles.Add(unArticle)
End While
'retorna-se o resultado
Return articles
Finally
' liberaçã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 uma solicitação de atualização
Try
': cria-se uma conexão com o BDD
connexion.Open()
'execução da consulta
Return sqlCommand.ExecuteNonQuery()
Finally
' liberação de recursos
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Recomenda-se ao leitor que interprete este código à luz dos comentários sobre a classe [ArticlesDaoPlainODBC] feitos anteriormente.
2.4.5.3. Geração do assembly da camada [dao]
O novo projeto do Visual Studio possui 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], gerado a partir do projeto [dao-oledb]
- modificamos o arquivo de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoOleDb]:
Comentários:
- na linha 7, o objeto [articlesdao] agora está associado a uma instância da classe [ ArticlesDaoOleDb]
- essa classe possui um construtor com um argumento: a string de conexão com o banco de dados OleDb ACCESS - linha 9
2.4.5.4.2. Tests
Estamos prontos para os testes. Com o auxílio do aplicativo [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 fato a classe [ArticlesDaoOleDb] que está sendo testada aqui. A captura de tela mostra que recuperamos corretamente os itens que havíamos inserido na tabela [ARTICLES]. Agora, vamos executar todos os testes:

O leitor que visualizar este documento na tela 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. Realizamos as seguintes alterações no conteúdo da pasta [runtime]:
- na pasta [bin], a DLL daantiga camada [dao] é substituída pela DLL da nova camada [dao] implementada pela classe [ArticlesDaoOleDb]
- em [runtime], o arquivo de configuração [web.config] é substituído por um arquivo que leva em conta a nova classe de implementação:
Comentários:
- as linhas 14 a 18 associam ao singleton [articlesDao] uma instância da nova classe [ArticlesDaoOleDb]. Essa é a única alteração.
Mantemos a mesma configuração do servidor web [Cassini] de antes. Inicializamos a tabela de artigos com os seguintes valores:

Certifique-se de que o banco de dados de artigos não esteja sendo utilizado por um programa como o Visual Studio ou o ACCESS. Com um navegador, acessamos o URL [http://localhost/webarticles/main.aspx]:

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

Os itens [pantalon] e [jupe] foram comprados e seus estoques foram reduzidos pela quantidade comprada. O item [manteau] não pôde ser comprado porque a quantidade solicitada excedia a quantidade em estoque. 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á utilizamos uma fonte de dados [Firebird], à qual acessamos por meio de um driver ODBC. Embora proporcionem grande reutilização ao código que os emprega, os drivers ODBC apresentam, no entanto, desempenho inferior ao dos drivers escritos especificamente para o SGBD em questão. O SGBD [Firebird] pode ser utilizado por meio de uma biblioteca de classes específicas que pode ser baixada no site do Firebird [http://firebird.sourceforge.net/]. A página de downloads oferece os seguintes links (abril de 2005):
![]()
O link [firebird-net-provider] é o link a ser utilizado para baixar as classes .NET de acesso ao Firebird SGBD. A instalação do pacote cria uma pasta semelhante à seguinte:

Dois elementos 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 essas classes
Posteriormente, para que um projeto do Visual Studio possa utilizar essas classes, faremos duas coisas:
- colocaremos o assembly [FirebirdSql.Data.Firebird.dll] na pasta [bin] do projeto
- adicionaremos 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. Portanto, indicaremos apenas as alterações feitas em relação a essa versão:
- as classes necessárias estão no espaço de nomes [FirebirdSql.Data.Firebird], em vez do espaço de nomes [System.Data.SqlClient]
- a conexão do tipo [SqlConnection] agora tem o tipo [FbConnection]
- os objetos [SqlCommand] agora têm o tipo [FbCommand]
- os objetos [SqlParameter] agora têm o tipo [FbParameter]
O construtor da classe aceita quatro parâmetros, com os quais constrói a string de conexão ao banco 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 host do SGBD
' databaseName: caminho de acesso ao banco de dados
' uid: identidade do usuário que está se conectando
' password: sua senha
...
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 da máquina host do SGBD Firebird
' databaseName: caminho de acesso ao banco de dados a ser utilizado
' uid: identidade do usuário que se conecta ao banco de dados
' password: sua senha
': recupera-se o nome do banco de dados passado como argumento
Me.databasePath = databasePath
': instanciamos a conexão
Dim connectString As String = String.Format("DataSource={0};Database={1};User={2};Password={3}", serveur, databasePath, uid, password)
connexion = New FbConnection(connectString)
' prepara-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
' seçã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
' seção exclusiva
SyncLock Me
' preparando a consulta de atualização do estoque
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
' seção exclusiva
SyncLock Me
Try
'executando a consulta de inserção
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
' seção exclusiva
SyncLock Me
Try
'executando a consulta SELECT
Dim articles As IList = executeQuery(selectAllCommand)
'retornando a lista
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
' seção exclusiva
SyncLock Me
' preparando a consulta SELECT
With selectSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
': a consulta é executada
Try
'executando a consulta
Dim articles As IList = executeQuery(selectSomeCommand)
'verificando se o artigo foi encontrado
If articles.Count = 0 Then Return Nothing
'retornamos 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
' seçã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
' a consulta é executada
Try
'executando a consulta de inserção
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
' seção exclusiva
SyncLock Me
' prepara-se a consulta de exclusão
With deleteSomeCommand.Parameters
.Clear()
.Add(New FbParameter("@id", idArticle))
End With
'é executada
Try
'executando a consulta de exclusã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 conexão com a BDD
connexion.Open()
': execução da consulta
myReader = query.ExecuteReader()
'declara-se uma lista de itens para ser retornada posteriormente
Dim articles As IList = New ArrayList
Dim unArticle As Article
While myReader.Read()
'prepara-se um item 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 item à lista
articles.Add(unArticle)
End While
'retorna-se o resultado
Return articles
Finally
' liberaçã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 uma solicitação de atualização
Try
': é criada uma conexão com o BDD
connexion.Open()
': executa-se a consulta
Return updateCommand.ExecuteNonQuery()
Finally
' liberação dos recursos
If Not connexion Is Nothing Then connexion.Close()
End Try
End Function
End Class
End Namespace
Recomenda-se ao leitor que interprete este código à luz dos comentários sobre a classe [ArticlesDaoSqlServer] feitos anteriormente.
2.5.3. Geração do assembly da camada [dao]
O novo projeto do Visual Studio possui a seguinte estrutura:

Observe-se a presença do assembly [FirebirdSql.Data.Firebird.dll] nas referências do projeto. Esse 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 e o [webarticles-dao.dll] pelo DLL e pelo [webarticles-dao.dll], gerados a partir do projeto [dao-firebird-provider]
- modificamos o arquivo de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoFirebirdProvider]:
Comentários:
- na linha 7, o objeto [articlesdao] agora está associado a uma instância da classe [ArticlesDaoFirebirdProvider]
- essa classe possui um construtor com quatro argumentos
- a máquina host do SGBD — linha 9
- o caminho de acesso ao banco de dados Firebird — linha 12
- o login do usuário que está se conectando — linha 15
- sua senha — linha 18
2.5.4.2. Testes
A tabela [ARTICLES] da fonte de dados é preenchida com os seguintes itens (use IBExpert):

Estamos prontos para os testes. Com o auxílio do aplicativo [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 fato a classe [ArticlesDaoFirebirdProvider] que está sendo testada aqui. A captura de tela mostra que recuperamos corretamente os itens que havíamos inserido na tabela [ARTICLES]. Agora, vamos executar todos os testes:

O leitor que visualizar este documento na tela poderá ver que todos os testes foram bem-sucedidos (cor verde). O que ele não pode ver é que os testes ocorreram de forma significativamente mais rápida do que com a base de artigos acessada por meio de um driver ODBC de nossa primeira implementação.
2.5.5. Integração da nova camada [dao] no aplicativo [webarticles]
Seguimos o procedimento já explicado duas vezes, notadamente no parágrafo 2.3.4. Realizamos as seguintes alterações no 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]. Também colocamos ali a DLL necessária para o Firebird [FirebirdSql.Data.Firebird.dll]:

- no [runtime], o arquivo de configuração [web.config] é substituído por um arquivo que leva em conta a nova classe de implementação:
Comentários:
- as linhas 14 a 27 associam ao singleton [articlesDao] uma instância da nova classe [ArticlesDaoFirebirdProvider]. Essa é a única modificação.
Estamos prontos para os testes. Configuramos o servidor web [Cassini] da mesma forma que nos testes anteriores. Inicializamos a tabela de artigos com os seguintes valores:

Usando um navegador, acessamos o URL [http://localhost/webarticles/main.aspx]:

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

Os itens [crayon bille] e [ramette 50 feuilles] foram comprados e seus estoques foram reduzidos pela quantidade comprada. O item [stylo plume] não pôde ser comprado porque a quantidade solicitada excedia a quantidade em estoque. 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] de nossa aplicação [webarticles]. Em todas as ocasiões, conseguimos integrar a nova camada [dao] à aplicação [webarticles] sem precisar recompilar as outras duas camadas, [web] e [domain]. Isso foi possível, vale lembrar, graças a duas escolhas arquitetônicas:
- o acesso às camadas por meio de interfaces
- a integração das camadas pelo Spring
Gostaríamos de ir um pouco além. Embora diferentes, nossas quatro implementações da camada [dao] apresentam semelhanças impressionantes. Uma vez escrita a primeira implementação, as outras três foram obtidas praticamente por meio de copiar e colar e substituição de certas palavras-chave por outras. A lógica, por sua vez, não foi alterada. Podemos nos perguntar se não seria possível ter uma implementação que nos livrasse dos diferentes modos de acesso aos dados. Utilizamos quatro:
- acesso por meio de um driver ODBC a uma fonte de dados ODBC
- acesso direto a um banco de dados SQL Server
- acesso por meio de um driver Ole Db a uma fonte de dados Ole Db
- acesso direto a um banco de dados Firebird
A ferramenta Ibatis SqlMap [[http://www.ibatis.com/] permite o desenvolvimento de camadas de acesso a dados independentes da natureza real da fonte de dados. O acesso aos dados é garantido por meio de:
- de arquivos de configuração nos quais são inseridas as informações que definem a fonte de dados e as operações que se deseja realizar sobre ela
- uma biblioteca de classes que se baseia nessas informações para acessar os dados
A ferramenta Ibatis SqlMap foi desenvolvida inicialmente para a plataforma Java. Sua portabilidade para a plataforma .NET é recente e, ao que parece, apresenta alguns bugs (opinião pessoal que exigiria uma verificação aprofundada). No entanto, como a ferramenta já provou seu valor 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 oferece os seguintes links:

Escolha o link [Stable Binaries], que nos leva para [SourceForge.net]. Siga o processo de download até o fim. Você receberá um arquivo zip contendo os seguintes arquivos:

Em um projeto do Visual Studio que utiliza o Ibatis SqlMap, é necessário fazer duas coisas:
- colocar os arquivos acima na pasta [bin] do projeto
- adicionar ao projeto uma referência a cada um desses arquivos
2.5.6.3. Os arquivos de configuração do Ibatis SqlMap
Uma fonte de dados [SqlMap] será definida por meio dos seguintes arquivos de configuração:
- providers.config: define as bibliotecas de classes a serem utilizadas para acessar os dados
- sqlmap.config: define as características da conexão a ser estabelecida
- arquivos de mapeamento: definem as operações a serem realizadas nos dados
A lógica desses arquivos é a seguinte:
- para acessar os dados, precisaremos de uma conexão. Para representá-la, já encontramos várias classes: OdbcConnection, SqlConnection, OleDbConnection, FbConnection. Também precisaremos de um objeto [Command] para enviar as solicitações SQL: OdbcCommand, SqlCommand, OleDbCommand, FbCommand. Etc. No arquivo [providers.config], definimos todas as classes de que precisamos.
- O arquivo [sqlmap.config] define essencialmente a string de conexão com o banco de dados que contém os dados. A conexão com o banco de dados será aberta por meio da instanciação da classe [Connection], definida em [providers.config], cujo construtor receberá a string de conexão definida em [sqlmap.config].
- Os arquivos 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 serem executadas. Estas são identificadas por um nome. O código .NET executa essas operações por meio de seus nomes, o que resulta na eliminação de todo o código SQL do código .NET.
2.5.6.4. Os arquivos de configuração do projeto [dao-sqlmap]
Vamos examinar, por meio de um exemplo, a natureza exata dos arquivos de configuração do SqlMap. Consideraremos o caso em que a fonte de dados é a fonte ODBC do Firebird, mencionada no parágrafo 2.3.3.1.
2.5.6.4.1. providers.config
O arquivo [providers.config] para uma fonte ODBC é este:
Comentários:
- um arquivo [providers.config] é distribuído junto com o pacote [SqlMap]. Ele oferece vários provedores de acesso (provider) padrão. O código acima provém diretamente desse arquivo.
- 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 mencionado na linha 8 deve estar acessível. Um arquivo [providers.config] pode ter várias tags <provider>.
- linha 8 — nome do assembly que contém as classes definidas nas linhas 9 a 15
- linha 9 — classe a ser usada para criar uma conexão
- linha 10 — classe a ser usada para criar um objeto [Command] para emissão de comandos SQL
- linha 11 — classe a ser usada para gerenciar 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 de sua propriedade [SelectCommand]
- linhas 16 a 19 — define-se como são gerenciados os comandos SQL configurados. Dependendo do caso, deve-se escrever, por exemplo:
ou
No primeiro caso, trata-se de parâmetros posicionais formais. Os valores efetivos desses parâmetros devem ser fornecidos na ordem dos parâmetros formais. No segundo caso, trata-se de parâmetros nomeados. Para atribuir um valor a um parâmetro desse tipo, especifica-se seu nome. A ordem não tem mais importância.
- linha 16 — indica-se que as fontes ODBC utilizam parâmetros posicionais
- linhas 17-19 – referem-se aos parâmetros nomeados. Aqui, não há nenhum.
Essas informações permitem que o SqlMap saiba, por exemplo, qual classe deve instanciar para criar uma conexão. Neste caso, será a classe [OdbcConnection] (linha 9).
2.5.6.4.2. sqlmap.config
O arquivo [providers.config] define as classes a serem utilizadas para acessar uma fonte ODBC. Ele não indica nenhuma fonte ODBC. É o arquivo [sqlmap.config] que faz isso:
Comentários:
- linha 3 — define-se um arquivo de propriedades [properties.xml]. Este arquivo define pares (chave, valor). As chaves podem ser quaisquer. O valor associado a uma chave C é obtido pela notação ${C} no arquivo [sqlmap.config]. Aqui está o arquivo [properties.xml], que será associado ao arquivo [sqlmap.config] anterior:
linha 3 — a chave [provider] é definida. Seu valor é o nome da tag <provider> a ser usada em [providers.config]
linha 4 — a chave [connectionString] é definida. Seu valor é a string de conexão a ser usada para estabelecer uma conexão com a 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 nenhuma delas será utilizada.
- linhas 9-13 — definem-se as características da fonte de dados:
- linha 10 — nome do <provider> de [providers.config] a ser utilizado
- linha 11 — string de conexão com a fonte de dados
- linha 12 – gerenciador de transações. Aqui não o utilizamos, mas mantivemos a linha mesmo assim, pois ela constava no arquivo de distribuição padrão.
- linhas 14-16 – lista dos arquivos que definem as operações SQL a serem realizadas na fonte de dados.
- linha 15 — define o arquivo de mapeamento [articles.xml]
2.5.6.4.3. articles.xml
Esse arquivo tem duas funções:
- definir um mapeamento de objetos das tabelas da fonte de dados. Nos casos mais simples, isso equivale a associar uma classe a uma linha de uma tabela.
- definir operações SQL configuradas e nomeá-las.
Utilizaremos o seguinte arquivo [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]. Esse mapeamento permite que a [SqlMap] construa o resultado de uma operação SQL SELECT. Cada linha de resultado do SELECT será colocada em um objeto [Article] de acordo com as regras do mapeamento.
- linha 5 — o mapeamento é delimitado pela tag <resultMap> e é nomeado pelo 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 ser utilizado é definido pelo atributo [resultMap="article"]. Portanto, faz-se referência aqui ao mapeamento das linhas 5 a 11
- linha 17 — texto do comando SQL a ser executado
- 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#, #nom#, #preço#, #estoqueatual#, #estoquemínimo#). Os valores desses cinco elementos serão provenientes 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, estoque atual, estoque mínimo) referenciadas pelo comando SQL configurado.
- linhas 29-31 - define-se o comando SQL Delete [deleteArticle], destinado a excluir um artigo cujo número #value# é conhecido. Esse número será passado como parâmetro: [parameterClass="int"]. Essa é uma regra geral. Quando o parâmetro é único, ele é referenciado pela palavra-chave #value# no texto do comando SQL.
- linhas 33-35 - define-se o comando SQL-Update [modifyArticle] destinado a modificar um item cujo número é conhecido. Assim como no comando [insertArticle], as cinco informações necessárias serão obtidas a partir 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 estoque, serão encontradas em um dicionário: [parameterClass="Hashtable"]. Este deverá ter duas chaves: id e mouvement. Serão os valores associados a essas duas chaves que serão utilizados no comando SQL.
2.5.6.4.4. Localização dos arquivos de configuração
Veremos duas situações diferentes:
- no caso de um teste Nunit, os arquivos de configuração do [SqlMap] serão colocados na mesma pasta que os binários testados.
- no caso de um aplicativo web, eles serão colocados na raiz do aplicativo.
2.5.6.5. O API do SqlMap
As classes do SqlMap estão contidas no DLL, que geralmente é colocado na pasta [bin] da aplicação:

As aplicações que utilizam as classes de SqlMap devem importar o namespace [IBatisNet.DataMapper]:
Todas as operações SQL são realizadas por meio de um singleton do tipo [Mapper], uma classe do namespace [IBatisNet.DataMapper ]. O singleton é obtido da seguinte maneira:
Para executar o comando SqlMap [getAllArticles], escreve-se:
- o método [QueryForList] permite obter o resultado de um comando SELECT em uma lista
- o primeiro parâmetro é o nome do comando SQL a ser executado (ver articles.xml)
- o segundo parâmetro é o parâmetro a ser transmitido à solicitação SQL. Deve corresponder ao atributo [parameterClass] do comando SqlMap. Em [articles.xml], temos [parameterClass=Nothing]. Portanto, passamos aqui um ponteiro nulo.
- O resultado é do tipo IList. Os objetos dessa lista são indicados pelo atributo [resultMap] do comando SQL-select: [resultMap="article"]. “artigo” é um nome de mapeamento:
A classe associada a esse 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 toda a tabela [ARTICLES] em uma única instrução. Se a tabela [ARTICLES] estiver vazia, obtemos um objeto [IList] com 0 elemento.
Para executar o comando SqlMap [getArticleById], escreveremos:
- o método [QueryForObject] permite obter o resultado de um comando SELECT que retorna apenas uma linha
- o primeiro parâmetro é o nome do comando SqlMap a ser executado
- o segundo parâmetro é o parâmetro a ser transmitido à solicitação SQL. Deve corresponder ao atributo [parameterClass] do comando SqlMap. No [articles.xml], temos o [parameterClass="int"]. Portanto, passamos aqui um inteiro que representa o número do artigo procurado.
- O resultado é do tipo Object. Se o SELECT não retornou nenhuma linha, obtém-se o ponteiro nulo (nothing) como resultado.
Para executar o comando SqlMap [insertArticle], escreveremos:
- o método [Insert] permite executar os comandos SQL e INSERT
- o primeiro parâmetro é o nome do comando SqlMap a ser executado
- o segundo parâmetro é o parâmetro a ser transmitido a ele. Deve corresponder ao atributo [parameterClass] do comando SqlMap. No [articles.xml], temos o [parameterClass="istia.st.articles.dao.Article"]. Portanto, aqui passamos 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 ser executado
- o segundo parâmetro é o parâmetro a ser transmitido a ele. Deve corresponder ao atributo [parameterClass] do comando SqlMap. No [articles.xml], temos o [parameterClass="int"]. Portanto, aqui passamos o número do item a ser excluído.
- O resultado do método [Delete] é o número de linhas excluídas
Da mesma forma, para executar o comando SqlMap [clearAllArticles], escrever-se-á:
Para executar o comando SqlMap [modifyArticle], digite:
- O método [Update] permite executar os comandos SQL e UPDATE
- o primeiro parâmetro é o nome do comando SqlMap a ser executado
- o segundo parâmetro é o parâmetro a ser transmitido a ele. Deve corresponder ao atributo [parameterClass] do comando SqlMap. No [articles.xml], temos o [parameterClass="istia.st.articles.dao.Article"]. Portanto, aqui passamos um objeto do tipo [istia.st.articles.dao.Article].
- O resultado do método [Update] é o número de linhas modificadas.
Da mesma forma, para executar o comando SqlMap [changerStockArticle], escrever-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 de [id, mouvement]. Portanto, aqui é passado um dicionário contendo essas duas chaves.
2.5.6.6. O código da classe [ArticlesDaoSqlMap]
Após as explicações anteriores, agora é possível 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 ser adicionado
' 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
' exclui um artigo
Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
SyncLock Me
Try
' id: ID do artigo a ser excluído
' exclusã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
' busca 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
' exclusã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 estoque 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 estoque está sendo alterado
' movimento: movimento de estoque
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
Recomenda-se ao leitor que analise este código à luz das explicações fornecidas para o API do SqlMap. É interessante observar que o uso do [SqlMap] reduziu significativamente a quantidade de código a ser escrito.
2.5.6.7. Geração do assembly da camada [dao]
O novo projeto do Visual Studio tem a seguinte estrutura:

Observe-se a presença dos “assembly” necessários para o SqlMap nas referências do projeto. Esses 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 arquivos DLL e [webarticles-dao.dll] pelos arquivos DLL e [webarticles-dao.dll] gerados a partir do projeto [dao-sqlmap].
- adicionamos os DLL necessários ao SqlMap, bem como os arquivos de configuração analisados [providers.config, sqlmap.config, properties.xml, articles.xml].
- Modificamos o arquivo de configuração [spring-config.xml] para instanciar a nova classe [ArticlesDaoSqlMap]:
Comentários:
- na linha 7, o objeto [articlesdao] agora está associado a uma instância da classe [ArticlesDaoSqlMap]
- essa classe não possui construtor. Será utilizado o construtor padrão.
2.5.6.8.2. Tests
A tabela [ARTICLES] da fonte de dados Firebird foi preenchida com os seguintes itens:

Estamos prontos para os testes. Com o auxílio do aplicativo [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 fato a classe [ArticlesDaoSqlMap] que está sendo testada aqui. A captura de tela mostra que recuperamos corretamente os itens que havíamos inserido na tabela [ARTICLES]. Agora, vamos executar todos os testes:

O leitor que estiver visualizando este documento na tela 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 dessas falhas são as seguintes:
- no [testArticleAbsent], solicita-se a modificação de um artigo que não existe. Para isso, utiliza-se o método [modifieArticle], que retorna o número de linhas modificadas 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 estoque em uma quantidade maior do que o próprio estoque. Para isso, utiliza-se o método [changerStockArticle], que retorna o número de linhas modificadas, 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 estoque 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á incorreto. Isso é possível. No entanto, ele é resultado da adaptação de uma classe Java que havia funcionado corretamente com a versão Java de SqlMap.
- a versão .NET do SqlMap apresenta falhas
- o driver ODBC do Firebird apresenta erros
- ...
Na falta de certezas, vamos contornar o obstáculo interceptando a famosa exceção [IBatisNet.Common.Exceptions.ConcurrentException]. O novo código da classe [ArticlesDaoSqlMap] fica assim:
As alterações estão nas linhas: 28, 41, 69. Para as operações SQL do tipo [UPDATE, DELETE], caso ocorra uma exceção do tipo [IBatisNet.Common.Exceptions.ConcurrentException], retorna-se 0 como resultado, indicando assim que nenhuma linha foi modificada ou excluída. Feito isso, o DLL do projeto é regenerado, colocado na pasta [test1] e os testes NUnit são reiniciados:

Desta vez deu certo. A partir de agora, trabalharemos com este DLL.
2.5.6.9. Integração da nova camada [dao] no aplicativo [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. Ela é utilizada aqui por meio do SqlMap.
Seguimos o procedimento descrito no parágrafo 2.3.4. Realizamos as seguintes alterações no 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 a ela as camadas DLL necessárias para o Firebird e a SqlMap:

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

- em [runtime], o arquivo de configuração [web.config] é substituído por um arquivo que leva 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]. Essa é a única modificação.
Estamos prontos para os testes. Configuramos o servidor web [Cassini] da mesma forma que nos testes anteriores. Inicializamos a tabela de artigos com os seguintes valores:

Usando um navegador, acessamos o URL [http://localhost/webarticles/main.aspx]:

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

Os itens [couteau] e [cuiller] foram comprados e seus estoques foram reduzidos pela quantidade comprada. O item [fourchette] não pôde ser comprado porque a quantidade solicitada excedia a quantidade em estoque. 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. Ela é utilizada aqui por meio de SqlMap. Seguimos o mesmo procedimento de antes. Realizamos as seguintes alterações no conteúdo da pasta [runtime]:
- o conteúdo da pasta [bin] não sofre alterações
- no [runtime], os arquivos de configuração de SqlMap e [providers.config, properties.xml] são alterados. Os arquivos de configuração de [sqlmap.config, articles.xml] não são alterados.
- O arquivo [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 acesso às fontes de dados do servidor SQL. Ele está integrado por padrão no arquivo modelo [providers.config] distribuído com o SqlMap.
- O arquivo [properties.xml] define o <provider> da fonte MSDE, bem como a cadeia de conexão desta:
<?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 arquivo de configuração [web.config] permanece inalterado.
Estamos prontos para os testes. O servidor web [Cassini] mantém sua configuração habitual. Inicializamos a tabela de artigos da fonte MSDE com [EMS MS SQL Manager]:

Usando um navegador, acessamos o URL e o [http://localhost/webarticles/main.aspx]:

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

Os itens [ballon foot] e [raquette tennis] foram comprados e seus estoques foram reduzidos pela quantidade comprada. O item [rollers] não pôde ser comprado porque a quantidade solicitada excedia a quantidade em estoque. 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. Ela é utilizada aqui por meio de SqlMap. Seguimos o mesmo procedimento de antes. Fazemos as seguintes alterações no conteúdo da pasta [runtime]:
- o conteúdo da pasta [bin] não sofre alterações
- em [runtime], os arquivos de configuração de SqlMap e [providers.config, properties.xml] são alterados. Os arquivos de configuração de [sqlmap.config, articles.xml] não são alterados.
- O arquivo [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 acessar as fontes de dados OleDb. Ele está integrado por padrão no arquivo modelo [providers.config], distribuído junto com o SqlMap.
- O arquivo [properties.xml] define o <provider> da fonte OleDb, bem como a cadeia de conexão desta:
<?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 arquivo de configuração [web.config] permanece inalterado.
Estamos prontos para os testes. O servidor web [Cassini] mantém sua configuração habitual. Inicializamos a tabela de artigos da fonte ACCESS da seguinte maneira:

Com um navegador, acessamos o URL [http://localhost/webarticles/main.aspx]:

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

Os itens [pantalon] e [jupe] foram comprados e seus estoques foram reduzidos pela quantidade comprada. O item [manteau] não pôde ser comprado porque a quantidade solicitada excedia a quantidade em estoque. Convidamos o leitor a realizar testes adicionais.
2.5.7. Conclusão
Encerramos aqui este longo artigo-tutorial. O que fizemos?
- Implementamos a camada [dao] de uma aplicação web de três camadas de quatro maneiras diferentes:
- utilizando as classes de acesso .NET para acessar as fontes ODBC
- usando as classes de acesso .NET para as fontes SQL Server
- usando as classes de acesso .NET para acessar as fontes OleDb
- usando classes de acesso de terceiros para acessar um banco de dados Firebird
- em cada uma dessas ocasiões, integramos a nova camada [dao] ao aplicativo [webarticles] de três camadas [web, domain, dao] sem recompilar nenhuma das camadas [web, domain]
- por fim, apresentamos 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 essa nova camada, pudemos utilizar sucessivamente as fontes de dados das implementações 1 a 3 anteriores. Isso foi feito de forma transparente por meio de arquivos de configuração.
- Demonstramos a grande flexibilidade que as ferramentas Spring e SqlMap proporcionam às aplicações web de três camadas.































































