19. Aplicação Web MVC numa arquitetura de 3 camadas – Exemplo 5, MySQL
19.1. A base de dados MySQL
Nesta versão, iremos armazenar a lista de pessoas numa tabela da base de dados MySQL 4.x. Utilizámos o pacote [Apache – MySQL – PHP] disponível no URL [http://www.easyphp.org]. A seguir, as capturas de ecrã são do cliente MySQL Manager Lite [http://www.sqlmanager.net/fr/products/mysql/manager], um cliente de administração gratuito para o SGBD MySQL.
A base de dados chama-se [dbpersonnes]. Contém uma tabela chamada [PERSONNES]:

A tabela [PERSONNES] conterá a lista de pessoas geridas pela aplicação web. Foi criada utilizando as seguintes instruções SQL:
O MySQL 4.x parece menos capaz do que os dois SGBDs anteriores. Não consegui adicionar restrições (verificações) à tabela.
- Linha 10: A tabela deve ser do tipo [InnoDB] e não [MyISAM], que não suporta transações.
- Linha 2: A chave primária é do tipo auto_increment. Se for inserida uma linha sem um valor para a coluna ID da tabela, o MySQL irá gerar automaticamente um número inteiro para essa coluna. Isto evita que tenhamos de gerar nós próprios as chaves primárias.
A tabela [PERSONNES] poderia ter o seguinte conteúdo:

Sabemos que, ao inserir um objeto [Person] através da nossa camada [DAO], o campo [id] deste objeto é igual a -1 antes da inserção e tem um valor diferente de -1 depois; este valor é a chave primária atribuída à nova linha inserida na tabela [PERSONNES]. Vejamos um exemplo para perceber como podemos determinar este valor.
![]() |
![]() |
A instrução SQL
permite-nos determinar o último valor inserido no campo ID da tabela. Deve ser executada após a inserção. Isto difere dos SGBDs [Firebird] e [Postgres], onde solicitávamos o valor da chave primária da pessoa adicionada antes da inserção. Iremos utilizá-la no ficheiro [people-mysql.xml], que contém as instruções SQL executadas na base de dados.
19.2. O projeto Eclipse para as camadas [DAO] e [service]
Para desenvolver as camadas [dao] e [service] da nossa aplicação com a base de dados MySQL, utilizaremos o seguinte projeto Eclipse [mvc-personnes-05]:

O projeto é um projeto Java simples, não um projeto web Tomcat.
Diretório [src]
Esta pasta contém o código-fonte das camadas [dao] e [service], bem como os ficheiros de configuração para estas duas camadas:

Todos os ficheiros com [mysql] no nome podem ou não ter sido modificados para as versões Firebird e Postgres. Abaixo, descrevemos aqueles que foram modificados.
Pasta [database]
Esta pasta contém o script para criar a base de dados MySQL para os utilizadores:
![]()
Pasta [lib]
Este diretório contém os ficheiros necessários para a aplicação:
![]() |
Note a presença do controlador JDBC do SGBD MySQL. Todos estes ficheiros fazem parte do classpath do projeto Eclipse.
19.3. A camada [dao]
A camada [dao] é a seguinte:

Apresentamos apenas as alterações em relação à versão [Firebird].
O ficheiro de mapeamento [person-mysql.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 class [Person] -->
<typeAlias alias="Personne.classe"
type="istia.st.mvc.personnes.entites.Personne"/>
<!-- mapping table [PERSONNES] - object [Person] -->
<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>
<!-- list of all persons -->
<select id="Personne.getAll" resultMap="Personne.map" > select ID, VERSION, NOM,
PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES</select>
<!-- get a specific person -->
<select id="Personne.getOne" resultMap="Personne.map" >select ID, VERSION, NOM,
PRENOM, DATENAISSANCE, MARIE, NBENFANTS FROM PERSONNES WHERE ID=#value#</select>
<!-- add a person -->
<insert id="Personne.insertOne" parameterClass="Personne.classe">
insert into
PERSONNES(VERSION, NOM, PRENOM, DATENAISSANCE, MARIE, NBENFANTS)
VALUES(#version#, #nom#, #prenom#, #dateNaissance#, #marie#,
#nbEnfants#)
<selectKey keyProperty="id">
select LAST_INSERT_ID() as value
</selectKey>
</insert>
<!-- update a person -->
<update id="Personne.updateOne" parameterClass="Personne.classe"> update
PERSONNES set VERSION=#version#+1, NOM=#nom#, PRENOM=#prenom#, DATENAISSANCE=#dateNaissance#,
MARIE=#marie#, NBENFANTS=#nbEnfants# WHERE ID=#id# and
VERSION=#version#</update>
<!-- delete a person -->
<delete id="Personne.deleteOne" parameterClass="int"> delete FROM PERSONNES WHERE
ID=#value# </delete>
<!-- obtain the value of the primary key [id] of the last person inserted -->
<select id="Personne.getNextId" resultClass="int">select
LAST_INSERT_ID()</select>
</sqlMap>
Este é o mesmo conteúdo que [people-firebird.xml], com as seguintes pequenas diferenças:
- a instrução SQL «Person.insertOne» foi alterada nas linhas 29–37:
- a instrução SQL de inserção é executada antes da instrução SELECT, que recupera o valor da chave primária da linha inserida
- a instrução SQL de inserção não tem um valor para a coluna ID na tabela [PERSONNES]
Isto reflete o exemplo de inserção que discutimos na Secção 19.1.
Note que isto pode ser uma fonte potencial de problemas entre threads simultâneas. Imagine duas threads, Th1 e Th2, a realizar uma inserção ao mesmo tempo. Há um total de quatro instruções SQL a serem executadas. Suponha que sejam executadas na seguinte ordem:
- inserir I1 por Th1
- insert I2 por Th2
- select S1 por Th1
- select S2 por Th2
No passo 3, Th1 recupera a chave primária gerada durante a última inserção, que é a chave de Th2 e não a sua própria. Não tenho a certeza se o método [insert] do iBATIS está protegido contra este cenário. Vamos assumir que lida com isto corretamente. Se não fosse esse o caso, teríamos de derivar a classe de implementação [DaoImplCommon] da camada [dao] para uma classe [DaoImplMySQL], onde o método [insertPersonne] seria sincronizado. Isto resolveria apenas o problema para os threads da nossa aplicação. Se, no exemplo acima, Th1 e Th2 forem threads de duas aplicações diferentes, o problema teria então de ser resolvido utilizando transações e um nível de isolamento adequado entre transações. O nível [serializable], no qual as transações são executadas como se estivessem a decorrer sequencialmente, seria adequado.
Note-se que este problema não existe com os SGBDs Firebird e Postgres, que executam o SELECT antes do INSERT. Por exemplo, considere a seguinte sequência:
- select S1 from Th1
- SELECT S2 from Th2
- insert I1 from Th1
- insert I2 from Th2
Nos passos 1 e 2, Th1 e Th2 recuperam valores de chave primária do mesmo gerador. Esta operação é normalmente atómica, e Th1 e Th2 irão recuperar dois valores diferentes. Se a operação não fosse atómica e Th1 e Th2 recuperassem dois valores idênticos, a inserção realizada no passo 4 por Th2 falharia devido a uma duplicata da chave primária. Este é um erro totalmente recuperável, e Th2 pode tentar novamente a inserção.
Vamos deixar a operação «Personne.insertOne» tal como está atualmente no ficheiro [personnes-mysql.xml], mas o leitor deve estar ciente de que existe aqui um potencial problema.
A classe de implementação [DaoImplCommon] da camada [dao] é a mesma das duas versões anteriores.
A configuração da camada [dao] foi adaptada ao SGBD [MySQL]. Assim, o ficheiro de configuração [spring-config-test-dao-mysql.xml] é o seguinte:
<?xml version="1.0" encoding="ISO_8859-1"?>
<!DOCTYPE beans SYSTEM "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- data source DBCP -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://localhost/dbpersonnes</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value></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-mysql.xml</value>
</property>
</bean>
<!-- the [dao] layer access classes -->
<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 [MySQL] [dbpersonnes], cujo administrador é [root] sem palavra-passe. O leitor deve modificar esta configuração de acordo com o seu próprio ambiente.
- linha 31: a classe [DaoImplCommon] é a classe de implementação para a camada [dao]
Com estas alterações feitas, podemos passar aos testes.
19.4. Testes para as camadas [dao] e [service]
Os testes para as camadas [dao] e [service] são os mesmos da versão [Firebird]. Os resultados obtidos são os seguintes:
![]() |
Podemos ver que os testes foram bem-sucedidos com a implementação [DaoImplCommon]. Não será necessário derivar esta classe, como era necessário com o SGBD [Firebird].
19.5. Testes da aplicação [Web]
Para testar a aplicação web com o SGBD [MySQL], criamos um projeto Eclipse [mvc-personnes-05B] de forma análoga à utilizada para criar o projeto [mvc-personnes-03B] com a base de dados Firebird (ver Secção 17.7). No entanto, tal como no Postgres, não precisamos de recriar os arquivos [personnes-dao.jar] e [personnes-service.jar], uma vez que não modificámos nenhuma classe.
Implementamos o projeto web [mvc-personnes-05B] no Tomcat:
![]() | ![]() |
O SGBD MySQL é iniciado. O conteúdo da tabela [PERSONNES] fica então da seguinte forma:

O Tomcat é então iniciado. Utilizando um navegador, solicitamos o URL [http://localhost:8080/mvc-personnes-05B]:

Adicionamos uma nova pessoa utilizando o link [Adicionar]:
![]() | ![]() |
Verificamos a adição na base de dados:

Convidamos o leitor a realizar outros testes [editar, eliminar].







