Skip to content

20. Aplicação web MVC numa arquitetura de três camadas – Exemplo 6, SQL Server Express

20.1. A base de dados SQL Server Express

Nesta versão, vamos instalar a lista de pessoas numa tabela da base de dados do SQL Server Express 2005, disponível no URL [http://msdn.microsoft.com/vstudio/express/sql/]. A seguir, as capturas de ecrã provêm do cliente EMS Manager Lite para o SQL Server Express [http://www.sqlmanager.net/fr/products/mssql/manager], um cliente de administração gratuito do SGBD SQL Server Express.

A base de dados chama-se [dbpersonnes]. Contém uma tabela [PERSONNES]:

Image

A tabela [PERSONNES] conterá a lista de pessoas geridas pela aplicação web. Foi criada com os seguintes comandos SQL:

CREATE TABLE [dbo].[PERSONNES] (
  [ID] int IDENTITY(1, 1) NOT NULL,
  [VERSION] int NOT NULL,
  [NOM] varchar(30) COLLATE French_CI_AS NOT NULL,
  [PRENOM] varchar(30) COLLATE French_CI_AS NOT NULL,
  [DATENAISSANCE] datetime NOT NULL,
  [MARIE] tinyint NOT NULL,
  [NBENFANTS] tinyint NOT NULL,
  PRIMARY KEY CLUSTERED ([ID]),
  CONSTRAINT [PERSONNES_ck_NOM] CHECK ([NOM]<>''),
  CONSTRAINT [PERSONNES_ck_PRENOM] CHECK ([PRENOM]<>''),
  CONSTRAINT [PERSONNES_ck_NBENFANTS] CHECK ([NBENFANTS]>=(0))
)
ON [PRIMARY]
GO
  • linha 2: a chave primária [ID] é do tipo inteiro. O atributo IDENTITY indica que, se for inserida uma linha sem valor para a coluna ID da tabela, o SQL Express irá gerar automaticamente um número inteiro para essa coluna. Em IDENTITY(1, 1), o primeiro parâmetro é o primeiro valor possível para a chave primária e o segundo é o incremento utilizado na geração dos números.

A tabela [PERSONNES] poderia ter o seguinte conteúdo:

Image

Sabemos que, aquando da inserção de um objeto [Personne] pela nossa camada [dao], o campo [id] desse objeto é igual a -1 antes da inserção e assume um valor diferente de -1 depois, sendo esse valor a chave primária atribuída à nova linha inserida na tabela [PERSONNES]. Vejamos, através de um exemplo, como podemos determinar esse valor.

A ordem SQL

SELECT @@IDENTITY

permite conhecer o último valor inserido no campo ID da tabela. Deve ser emitida após a inserção. Esta é uma diferença em relação aos SGBD, [Firebird] e [Postgres], nos quais se solicitava o valor da chave primária da pessoa adicionada antes da inserção, mas é semelhante à geração da chave primária dos SGBD e MySQL. Iremos utilizá-la no ficheiro [personnes-sqlexpress.xml], que reúne as ordens SQL emitidas na base de dados.

20.2. O projeto Eclipse das camadas [dao] e [service]

Para desenvolver as camadas [dao] e [service] da nossa aplicação com a base de dados [SQL Server Express], utilizaremos o seguinte projeto Eclipse [mvc-personnes-06]:

Image

O projeto é um projeto Java simples, não um projeto web Tomcat.


Pasta [src]


Esta pasta contém os códigos-fonte das camadas [dao] e [service]:

Image

Todos os ficheiros cujo nome inclua «[sqlexpress]» podem ou não ter sofrido alterações em relação às versões Firebird, Postgres e MySQL. A seguir, descrevemos apenas aqueles que foram alterados.


Pasta [database]


Esta pasta contém o script de criação da base de dados SQL Express das pessoas:

Image

-- SQL Manager 2005 Lite para o servidor SQL (2.2.0.1)
-- ---------------------------------------
-- Host      : (local)\SQLEXPRESS
-- Base de dados: dbpersonnes


--
-- Estrutura da tabela PERSONNES: 
--

CREATE TABLE [dbo].[PERSONNES] (
  [ID] int IDENTITY(1, 1) NOT NULL,
  [VERSION] int NOT NULL,
  [NOM] varchar(30) COLLATE French_CI_AS NOT NULL,
  [PRENOM] varchar(30) COLLATE French_CI_AS NOT NULL,
  [DATENAISSANCE] datetime NOT NULL,
  [MARIE] tinyint NOT NULL,
  [NBENFANTS] tinyint NOT NULL,
  CONSTRAINT [PERSONNES_ck_NBENFANTS] CHECK ([NBENFANTS]>=(0)),
  CONSTRAINT [PERSONNES_ck_NOM] CHECK ([NOM]<>''),
  CONSTRAINT [PERSONNES_ck_PRENOM] CHECK ([PRENOM]<>'')
)
ON [PRIMARY]
GO

--
-- Dados da tabela PERSONNES  (LIMIT 0,500)
--

SET IDENTITY_INSERT [dbo].[PERSONNES] ON
GO

INSERT INTO [dbo].[PERSONNES] ([ID], [VERSION], [NOM], [PRENOM], [DATENAISSANCE], [MARIE], [NBENFANTS])
VALUES 
  (1, 1, 'Major', 'Joachim', '19541113', 1, 2)
GO

INSERT INTO [dbo].[PERSONNES] ([ID], [VERSION], [NOM], [PRENOM], [DATENAISSANCE], [MARIE], [NBENFANTS])
VALUES 
  (2, 1, 'Humbort', 'Mélanie', '19850212', 0, 1)
GO

INSERT INTO [dbo].[PERSONNES] ([ID], [VERSION], [NOM], [PRENOM], [DATENAISSANCE], [MARIE], [NBENFANTS])
VALUES 
  (3, 1, 'Lemarchand', 'Charles', '19860301', 0, 0)
GO

SET IDENTITY_INSERT [dbo].[PERSONNES] OFF
GO

--
-- Definição dos índices: 
--

ALTER TABLE [dbo].[PERSONNES]
ADD PRIMARY KEY CLUSTERED ([ID])
WITH (
  PAD_INDEX = OFF,
  IGNORE_DUP_KEY = OFF,
  STATISTICS_NORECOMPUTE = OFF,
  ALLOW_ROW_LOCKS = ON,
  ALLOW_PAGE_LOCKS = ON)
ON [PRIMARY]
GO

Pasta [lib]


Esta pasta contém os ficheiros necessários para a aplicação:

De salientar a presença do controlador JDBC [sqljdbc.jar], do SGBD e do [Sql Server Express]. Todos estes ficheiros fazem parte do Classpath do projeto Eclipse.


20.3. A camada [dao]

A camada [dao] é a seguinte:

Image

Apresentamos apenas as alterações em relação à versão [Firebird].

O ficheiro de mapeamento [personne-sqlexpress.xml] é o seguinte:


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

<!DOCTYPE sqlMap
    PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
    "http://www.ibatis.com/dtd/sql-map-2.dtd">

<sqlMap>
    <!-- alias da classe [Personne] -->
    <typeAlias alias="Personne.classe" 
        type="istia.st.mvc.personnes.entites.Personne"/>
    <!-- tabela de mapeamento [PERSONNES] - objeto [Personne] -->
    <resultMap id="Personne.map" 
        class="istia.st.mvc.personnes.entites.Personne">
        <result property="id" column="ID" />
        <result property="version" column="VERSION" />
        <result property="nom" column="NOM"/>
        <result property="prenom" column="PRENOM"/>
        <result property="dateNaissance" column="DATENAISSANCE"/>
        <result property="marie" column="MARIE"/>
        <result property="nbEnfants" column="NBENFANTS"/>
    </resultMap>
    <!-- lista de todas as pessoas -->
    <select id="Personne.getAll" resultMap="Personne.map" > select ID, VERSION, NOM, 
        PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES</select>
    <!-- obter uma pessoa específica -->
        <select id="Personne.getOne" resultMap="Personne.map" >select ID, VERSION, NOM, 
        PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES WHERE ID=#valor#</select>
    <!-- adicionar uma pessoa -->
    <insert id="Personne.insertOne" parameterClass="Personne.classe">
        insert into 
        PERSONNES(VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS) 
        VALUES(#versão#, #apelido#, #nome próprio#, #dateNaissance#, #casada#, 
         #nbEnfants#) 
        <selectKey keyProperty="id">
            select @@IDENTITY as value
        </selectKey>         
    </insert>
    <!-- atualizar um utilizador -->
    <update id="Personne.updateOne" parameterClass="Personne.classe"> update 
        PERSONNES set VERSION=#versão#+1, NOM=#apelido#, PRENOM=#nome#, DATENAISSANCE=#dateNaissance#, 
        MARIE=#marie#, NBENFANTS=#nbEnfants# WHERE ID=#id# e 
        VERSION=#versão#</update>
    <!-- eliminar uma pessoa -->
    <delete id="Personne.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE 
        ID=#valor# </delete>
    <!-- obter o valor da chave primária [id] da última pessoa inserida -->
    <select id="Personne.getNextId" resultClass="int">select 
        LAST_INSERT_ID()</select>
</sqlMap>

Tem o mesmo conteúdo que o [personnes-firebird.xml], com as seguintes diferenças:

  • a ordem SQL «Personne.insertOne» sofreu alterações nas linhas 29-37:
  • a ordem de inserção SQL é executada antes da ordem SELECT, o que permitirá recuperar o valor da chave primária da linha inserida
  • o comando de inserção SQL não tem valor para a coluna ID da tabela [PERSONNES]

Isto reflete o exemplo de inserção que comentámos no parágrafo 20.1. De notar que aqui se repete o problema das inserções simultâneas por threads diferentes, descrito para MySQL no parágrafo 19.3.

A classe de implementação [DaoImplCommon] da camada [dao] é a das três versões anteriores.

A configuração da camada [dao] foi adaptada às camadas SGBD e [SQL Express]. Assim, o ficheiro de configuração [spring-config-test-dao-sqlexpress.xml] é o seguinte:


<?xml version="1.0" encoding="ISO_8859-1"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- a fonte de dados DBCP -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
        destroy-method="close">
        <property name="driverClassName">
            <value>com.microsoft.sqlserver.jdbc.SQLServerDriver</value>
        </property>
        <property name="url">
            <value>jdbc:sqlserver://localhost\\SQLEXPRESS:4000;databaseName=dbpersonnes</value>
        </property>
        <property name="username">
            <value>sa</value>
        </property>
        <property name="password">
            <value>msde</value>
        </property>
    </bean>
    <!-- SqlMapCllient -->
    <bean id="sqlMapClient" 
        class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
        <property name="dataSource">
            <ref local="dataSource"/>
        </property>
        <property name="configLocation">
            <value>classpath:sql-map-config-sqlexpress.xml</value>
        </property>
    </bean>
    <!-- a classe de acesso à camada [dao] -->
    <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImplCommon">
        <property name="sqlMapClient">
            <ref local="sqlMapClient"/>
        </property>
    </bean>
</beans>
  • linhas 5-19: o bean [dataSource] refere-se agora à base de dados [SQL Express] [dbpersonnes], cujo administrador é [sa] com a palavra-passe [msde]. O leitor deverá alterar esta configuração de acordo com o seu próprio ambiente.
  • linha 31: a classe [DaoImplCommon] é a classe de implementação da camada [dao]

A linha 11 merece algumas explicações:

            <value>jdbc:sqlserver://localhost\\SQLEXPRESS:4000;databaseName=dbpersonnes</value>
  • //localhost: indica que o servidor SQL Express se encontra na mesma máquina que a nossa aplicação Java
  • \\SQLEXPRESS: é o nome de uma instância do SQL Server. Parece que várias instâncias podem ser executadas em simultâneo. Por isso, parece lógico nomear a instância à qual nos dirigimos. Este nome pode ser obtido através do [SQL Server Configuration Manager], normalmente instalado em simultâneo com o SQL Express:

Image

Image

  • 4000: porta de escuta do SQL Express. Isto depende da configuração do servidor. Por predefinição, funciona com portas dinâmicas, pelo que não são conhecidas antecipadamente. Por isso, não se especifica nenhuma porta na URL JDBC. Neste caso, trabalhámos com uma porta fixa, a porta 4000. Isto é conseguido através da seguinte configuração:
  • O atributo dataBaseName define a base de dados com a qual se pretende trabalhar. É a que foi criada com o cliente EMS:

Image

Depois de efetuadas estas alterações, podemos passar aos testes.

20.4. Testes das camadas [dao] e [service]

Os testes das camadas [dao] e [service] são os mesmos que para a versão [Firebird]. Os resultados obtidos são os seguintes:

Verifica-se que os testes foram concluídos com sucesso com a implementação [DaoImplCommon]. Não será necessário derivar esta classe, como tinha sido necessário fazer com as versões SGBD e [Firebird].

20.5. Testes da aplicação [web]

Para testar a aplicação web com o SGBD e o [SQL Server Express], criamos um projeto Eclipse [mvc-personnes-06B] de forma semelhante à utilizada para criar os projetos web anteriores.

Implantamos o projeto web [mvc-personnes-05B] no Tomcat:

O servidor Express SGBD SQL é iniciado. O conteúdo da tabela [PERSONNES] é então o seguinte:

Image

O Tomcat é, por sua vez, iniciado. Através de um navegador, acedemos à URL [http://localhost:8080/mvc-personnes-06B]:

Image

Adicionamos uma nova pessoa através do link [Ajout]:

Verificamos a adição na base de dados:

Image

O leitor é convidado a realizar outros testes com o [modification, suppression].