Skip to content

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

Image

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:

  1. 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.
  2. 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.
  3. 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
  4. 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.
  5. 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:

  1. as classes de negócio
  2. as classes de acesso aos dados
  3. 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);
id
chave primária que identifica um item de forma exclusiva
nom
nome do artigo
prix
seu preço
stockactuel
estoque atual
stockminimum
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:

Image

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]

Image

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:

  1. um construtor que permite definir as 5 informações de um artigo: [id, nom, prix, stockactuel, stockminimum]
  2. propriedades públicas que permitem ler e gravar as 5 informações.
  3. uma verificação dos dados inseridos no artigo. Caso haja dados incorretos, é lançada uma exceção.
  4. 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:

getAllArticles
retorna todos os itens da fonte de dados
clearAllArticles
esvazia a fonte de dados
getArticleById
retorna o objeto [Article] identificado por seu número
ajouteArticle
permite adicionar um item à fonte de dados
modifieArticle
permite modificar um item da fonte de dados
supprimerArticle
permite excluir um item da fonte de dados
changerStockArticle
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:

Imports System
Imports System.Collections
Imports System.Data.Odbc

Namespace istia.st.articles.dao

    Public Class ArticlesDaoPlainODBC
        Implements istia.st.articles.dao.IArticlesDao

         ' campos privados
        Private connexion As OdbcConnection = Nothing
        Private DSN As String
        Private insertCommand As OdbcCommand
        Private updatecommand As OdbcCommand
        Private deleteSomeCommand As OdbcCommand
        Private selectSomeCommand As OdbcCommand
        Private updateStockCommand As OdbcCommand
        Private deleteAllCommand As OdbcCommand
        Private selectAllCommand As OdbcCommand

         ' fabricante
        Public Sub New(ByVal DSN As String, ByVal uid As String, ByVal password As String)
             ' DSN: nome da fonte ODBC
             ' uid: identidade do usuário
             ' senha: sua senha
...
        End Sub

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
...
        End Function

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
...
        End Function

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
...
        End Sub

        Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
...
        End Function

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
...
        End Function

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
...
        End Function

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
....
        End Function

        Private Function executeQuery(ByVal query As OdbcCommand) As IList
             ' execução de uma consulta SELECT 
....
        End Function

        Private Function executeUpdate(ByVal sqlCommand As OdbcCommand) As Integer
....
    End Class

End Namespace

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

         ' construtor
        Public Sub New(ByVal DSN As String, ByVal uid As String, ByVal password As String)
             ' DSN: nome da fonte ODBC
             ' uid: identidade do usuário
             ' senha: sua senha

             ': recupera-se o nome do banco de dados passado como argumento
            Me.DSN = DSN
            Dim connectString As String = String.Format("DSN={0};UID={1};PASSWORD={2}", DSN, uid, password)
             ': instanciamos a conexão
            connexion = New OdbcConnection(connectString)
             ' prepara-se as consultas SQL
            insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
            updatecommand = New OdbcCommand("update ARTICLES set nom=?, prix=?, stockactuel=?, stockminimum=? where id=?", connexion)
            deleteSomeCommand = New OdbcCommand("delete from ARTICLES where id=?", connexion)
            selectSomeCommand = New OdbcCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=?", connexion)
            updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
            selectAllCommand = New OdbcCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
            deleteAllCommand = New OdbcCommand("delete from ARTICLES", connexion)
        End Sub

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

        Private Function executeQuery(ByVal query As OdbcCommand) 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 OdbcDataReader = Nothing
            Try
                 ': criação de 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

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

        Private Function executeUpdate(ByVal sqlCommand As OdbcCommand) 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

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

        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 OdbcParameter("id", unArticle.id))
                    .Add(New OdbcParameter("nom", unArticle.nom))
                    .Add(New OdbcParameter("prix", unArticle.prix))
                    .Add(New OdbcParameter("stockactuel", unArticle.stockactuel))
                    .Add(New OdbcParameter("stockminimum", unArticle.stockminimum))
                End With
                Try
                     'executa-se
                    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

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

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
             ' seção exclusiva
            SyncLock Me
                 ' preparando a consulta de atualização
                With updatecommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("nom", unArticle.nom))
                    .Add(New OdbcParameter("prix", unArticle.prix))
                    .Add(New OdbcParameter("stockactuel", unArticle.stockactuel))
                    .Add(New OdbcParameter("stockminimum", unArticle.stockminimum))
                    .Add(New OdbcParameter("id", unArticle.id))
                End With
                 ' executando a consulta
                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

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

        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 OdbcParameter("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

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

            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

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

        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 OdbcParameter("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

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

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' seção exclusiva
            SyncLock Me
                Try
                     ': executa-se a consulta de exclusã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

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

        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 OdbcParameter("mvt1", mouvement))
                    .Add(New OdbcParameter("id", idArticle))
                    .Add(New OdbcParameter("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

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:

Image

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:

Image

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]:
<?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.ArticlesDaoPlainODBC, webarticles-dao">
        <constructor-arg index="0">
            <value>odbc-firebird-articles</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>SYSDBA</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>masterkey</value>
        </constructor-arg>
    </object>
</objects>

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]:

Image

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:

Image

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:

<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, webarticles-dao">
                <constructor-arg index="0">
                    <value>odbc-firebird-articles</value>
                </constructor-arg>
                <constructor-arg index="1">
                    <value>SYSDBA</value>
                </constructor-arg>
                <constructor-arg index="2">
                    <value>masterkey</value>
                </constructor-arg>
            </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

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:

Image

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]:

Image

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

Image

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:

Image

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]:

Image

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:

Image

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]:
<?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.ArticlesDaoSqlServer, webarticles-dao">
        <constructor-arg index="0">
            <value>portable1_tahe\msde140405</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>dbarticles</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>admarticles</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>mdparticles</value>
        </constructor-arg>
    </object>
</objects>

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]:

Image

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:

Image

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:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoSqlServer, webarticles-dao">
        <constructor-arg index="0">
            <value>portable1_tahe\msde140405</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>dbarticles</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>admarticles</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>mdparticles</value>
        </constructor-arg>
    </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

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:

Image

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]:

Image

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

Image

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]:

Image

  • 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:

Image

  • 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:

Image

  • A estrutura da tabela é a seguinte:
id
numérico - inteiro - chave primária
nom
texto - 20 caracteres -
prix
numérico - real duplo
stockactuel
numérico - inteiro
stockminimum
numérico - inteiro
  • Voltemos ao Visual Studio e criemos uma nova conexão, conforme explicado anteriormente:

Image

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

Image

  • 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:

Image

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

Image

  • É 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:

Image

  • É 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:
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;

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:

Image

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]:
<?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.ArticlesDaoOleDb, webarticles-dao">
        <constructor-arg index="0">
            <value>Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;</value>
        </constructor-arg>
    </object>
</objects>

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]:

Image

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:

Image

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:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoOleDb, webarticles-dao">
                <constructor-arg index="0">
                    <value>Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;</value>
                </constructor-arg>
            </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

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:

Image

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]:

Image

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

Image

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):

Image

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:

Image

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:

Image

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]:
<?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.ArticlesDaoFirebirdProvider, webarticles-dao">
        <constructor-arg index="0">
            <value>localhost</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>D:\data\serge\databases\firebird\dbarticles2.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>sysdba</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>masterkey</value>
        </constructor-arg>
    </object>
</objects>

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):

Image

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]:

Image

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:

Image

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]:

Image

  • no [runtime], o arquivo de configuração [web.config] é substituído por um arquivo que leva em conta a nova classe de implementação:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoFirebirdProvider, webarticles-dao">
                <constructor-arg index="0">
                    <value>localhost</value>
                </constructor-arg>
                <constructor-arg index="1">
                    <value>D:\data\serge\databases\firebird\dbarticles2.gdb</value>
                </constructor-arg>
                <constructor-arg index="2">
                    <value>sysdba</value>
                </constructor-arg>
                <constructor-arg index="3">
                    <value>masterkey</value>
                </constructor-arg>
            </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

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:

Image

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

Image

Agora, vamos verificar o conteúdo da tabela [ARTICLES]:

Image

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:

Image

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:

Image

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:

  1. providers.config: define as bibliotecas de classes a serem utilizadas para acessar os dados
  2. sqlmap.config: define as características da conexão a ser estabelecida
  3. 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:

<?xml version="1.0" encoding="utf-8" ?> 

<providers>
    <clear/>
    <provider 
        name="Odbc1.1" 
        enabled="true" 
        assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
        connectionClass="System.Data.Odbc.OdbcConnection" 
        commandClass="System.Data.Odbc.OdbcCommand" 
        parameterClass="System.Data.Odbc.OdbcParameter" 
        parameterDbTypeClass="System.Data.Odbc.OdbcType" 
        parameterDbTypeProperty="OdbcType" 
        dataAdapterClass="System.Data.Odbc.OdbcDataAdapter" 
        commandBuilderClass="System.Data.Odbc.OdbcCommandBuilder" 
        usePositionalParameters = "true"
        useParameterPrefixInSql = "false"
        useParameterPrefixInParameter = "false"
        parameterPrefix = "@"
    />
</providers>

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:
insert into ARTICLES(id,nom,prix,stockactuel,stockminimum) values (?,?,?,?,?)

ou

insert into ARTICLES(id,nom,prix,stockactuel,stockminimum) values (@id,@nom,@prix,@sa,@sm)

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:

<?xml version="1.0" encoding="utf-8" ?>
<sqlMapConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Schemas\SqlMapConfig.xsd">
    <properties resource="properties.xml"/>
    <settings>
        <setting useStatementNamespaces="false" />
        <setting cacheModelsEnabled="false" />
    </settings>
     <!-- ==== fonte de dados ========= -    ->
    <database>
        <provider name="${provider}"/>
        <dataSource name="sqlmaparticles"  connectionString="${connectionString}"/>
        <transactionManager type="ADO/SWC" />
    </database>
    <sqlMaps>
        <sqlMap resource="articles.xml" />
    </sqlMaps>
</sqlMapConfig>

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:
1
2
3
4
5
<?xml version="1.0" encoding="utf-8" ?>
<settings>
    <add key="provider" value="Odbc1.1" />
    <add key="connectionString" value="DSN=odbc-firebird-articles;UID=SYSDBA;PASSWORD=masterkey" />
</settings>

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]:

<?xml version="1.0" encoding="iso-8859-1" ?>
<sqlMap namespace="Articles" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="SqlMap.xsd">
     <!-- os resultMap -->
    <resultMaps>
        <resultMap id="article" class="istia.st.articles.dao.Article">
            <result property="id" column="ID" />
            <result property="nom" column="NOM" />
            <result property="prix" column="PRIX" />
            <result property="stockactuel" column="STOCKACTUEL" />
            <result property="stockminimum" column="STOCKMINIMUM" />
        </resultMap>
    </resultMaps>
     <!-- as consultas SQL -->
    <statements>
         <!-- obtenção de todos os artigos -->
        <select id="getAllArticles" resultMap="article">
                select ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM FROM ARTICLES
            </select> 
         <!-- exclusão de todos os artigos-->
        <delete id="clearAllArticles">
                delete from ARTICLES
            </delete> 
         <!-- inserção de um artigo -->
        <insert id="insertArticle" parameterClass="istia.st.articles.dao.Article">
                insert into ARTICLES (id, nom, prix,stockactuel, stockminimum) values
                ( #(id#, #nome#, #preço#, #estoque atual#, #estoque mínimo#)
            </insert>
         <!-- exclusão de um item -->
        <delete id="deleteArticle" parameterClass="int">
                delete FROM ARTICLES where ID= #valor#
            </delete>
         <!-- alteração de um artigo -->
        <update id="modifyArticle" parameterClass="istia.st.articles.dao.Article">
                update ARTICLES set NOM= #nome#, PRIX= #preço#, STOCKACTUEL= #estoque atual# ,STOCKMINIMUM= #estoque mínimo# onde ID= #id#
            </update>
         <!-- busca por um item específico -->
        <select id="getArticleById" resultMap="article" parameterClass="int">
                select ID, NOM, PRIX,    STOCKACTUEL, STOCKMINIMUM FROM ARTICLES where ID= #valor#
            </select>
         <!-- alteração do estoque de um item -->
        <update id="changerStockArticle" parameterClass="Hashtable">
                update ARTICLES set STOCKACTUEL=(STOCKACTUEL + #movimento#) onde ID=#id# e ((STOCKACTUEL + #movimento#) >=0)
      </update>
    </statements>
</sqlMap>

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:

Image

As aplicações que utilizam as classes de SqlMap devem importar o namespace [IBatisNet.DataMapper]:

Imports 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:

        Dim mappeur As SqlMapper = Mapper.Instance

Para executar o comando SqlMap [getAllArticles], escreve-se:

                    dim articles as IList=mappeur.QueryForList("getAllArticles", Nothing)
  • 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:
<resultMap id="article" class="istia.st.articles.dao.Article">

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:

dim unArticle as Article=CType(mappeur.QueryForObject("getArticleById", idArticle), Article)
  • 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:

                    mappeur.Insert("insertArticle", unArticle)
  • 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-á:

                    dim nbArticles as Integer=mappeur.Delete("deleteArticle", idArticle)
  • 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-á:

                    dim nbArticles as Integer=mappeur.Delete("clearAllArticles", nothing)

Para executar o comando SqlMap [modifyArticle], digite:

                    dim nbArticles as Integer=mappeur.Update("modifyArticle", unArticle)
  • 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:

Image

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]:
1
2
3
4
5
6
7
8
<?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.ArticlesDaoSqlMap, webarticles-dao"/>
</objects>

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:

Image

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]:

Image

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:

Image

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:

  1. 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.
  2. a versão .NET do SqlMap apresenta falhas
  3. o driver ODBC do Firebird apresenta erros
  4. ...

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:

....
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
...
        End Function

         ' adição de um artigo
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
...
        End Function

         ' exclui um item
        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
                    If ex.GetType.Equals(GetType(IBatisNet.Common.Exceptions.ConcurrentException)) Then Return 0
                    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
                    If ex.GetType.Equals(GetType(IBatisNet.Common.Exceptions.ConcurrentException)) Then Return 0
                    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
...
        End Function

         ' exclusão de todos os itens
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
....
        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
                    If ex.GetType.Equals(GetType(IBatisNet.Common.Exceptions.ConcurrentException)) Then Return 0
                    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

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:

Image

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:

Image

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

Image

  • em [runtime], o arquivo de configuração [web.config] é substituído por um arquivo que leva em conta a nova classe de implementação:
<?xml version="1.0" encoding="iso-8859-1" ?>
<configuration>
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
    <spring>
        <context type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoSqlMap, webarticles-dao"/>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

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:

Image

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

Image

Agora, vamos verificar o conteúdo da tabela [ARTICLES]:

Image

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]:

Image

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

Image

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

Image

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:

Image

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

Image

Agora, vamos verificar o conteúdo da tabela [ARTICLES] com:

Image

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:
    1. utilizando as classes de acesso .NET para acessar as fontes ODBC
    2. usando as classes de acesso .NET para as fontes SQL Server
    3. usando as classes de acesso .NET para acessar as fontes OleDb
    4. 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.