7. [TD]: Implementação da camada [DAO] do TD com o API JDBC
Palavras-chave: bancos de dados relacionais, API, JDBC, SQLException.
Vamos revisar a arquitetura em camadas do nosso aplicativo:
![]() |
Os dados necessários para a eleição são armazenados em um banco de dados MySQL [dbelections]
7.1. Support
![]() |
A pasta [support / chap-07] [1] contém:
- os projetos Eclipse deste capítulo [2];
- o script SQL para a criação das bases de dados MySQL, [dbelections] e [3];
7.2. O banco de dados [dbelections]
Tarefa a ser realizada: crie as bases de dados MySQL e [dblelections] seguindo o procedimento descrito no parágrafo 6.4.2.
O banco de dados [dbelections] é um banco de dados MySQL com duas tabelas:
![]() |
A tabela [conf] contém as informações de configuração da eleição:
![]() |
- [id]: chave primária autoincrementada;
- [version]: número da versão do registro — pode ser ignorado aqui;
- [sap]: número de vagas a serem preenchidas;
- [seuilelectoral]: o limite abaixo do qual uma lista é eliminada;
Seu conteúdo é o seguinte:
![]() |
A tabela [listes] contém as listas de candidatos da eleição:
![]() |
- [id]: chave primária autoincrementada;
- [version]: número da versão do registro — pode ser ignorado aqui;
- [nom]: nome da lista;
- [voix]: voz da lista — só é conhecida após a entrada do usuário na camada [présentation];
- [sieges]: número de cadeiras obtidas — só é conhecido após o cálculo da camada [métier];
- [elimine]: igual a 1 se a lista for eliminada, igual a 0 caso contrário — só é conhecido após o cálculo da camada [métier];
O conteúdo da tabela [listes] é o seguinte:
![]() |
O script SQL para gerar a base [dbelections] chama-se [dbelections.sql] e está no servidor. Seu código é o seguinte:
-- phpMyAdmin SQL Dump
-- versão 4.0.4
-- http://www.phpmyadmin.net
--
-- Cliente: localhost
-- Gerado em: quarta-feira, 11 de março de 2015, às 12h20
-- Versão do servidor: 5.6.12-log
-- Versão do PHP: 5.4.12
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Banco de dados: `dbelections`
--
CREATE DATABASE IF NOT EXISTS `dbelections` DEFAULT CHARACTER SET utf8 COLLATE utf8_swedish_ci;
USE `dbelections`;
-- --------------------------------------------------------
--
-- Estrutura da tabela `conf`
--
CREATE TABLE IF NOT EXISTS `conf` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`version` int(11) NOT NULL DEFAULT '1',
`sap` tinyint(4) NOT NULL,
`seuilelectoral` double NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci AUTO_INCREMENT=2 ;
--
-- Conteúdo da tabela `conf`
--
INSERT INTO `conf` (`id`, `version`, `sap`, `seuilelectoral`) VALUES
(1, 1, 6, 0.05);
-- --------------------------------------------------------
--
-- Estrutura da tabela `listes`
--
CREATE TABLE IF NOT EXISTS `listes` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`version` int(11) NOT NULL DEFAULT '1',
`nom` varchar(20) COLLATE utf8_swedish_ci NOT NULL,
`voix` int(11) NOT NULL,
`sieges` int(11) NOT NULL,
`elimine` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `nom` (`nom`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci AUTO_INCREMENT=8 ;
--
-- Conteúdo da tabela `listes`
--
INSERT INTO `listes` (`id`, `version`, `nom`, `voix`, `sieges`, `elimine`) VALUES
(1, 21, 'A', 10, 1, 0),
(2, 22, 'B', 20, 2, 0),
(3, 21, 'C', 30, 3, 0),
(4, 13, 'D', 40, 3, 0),
(5, 17, 'E', 50, 6, 0),
(6, 18, 'F', 60, 1, 0),
(7, 19, 'G', 70, 2, 0);
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
7.3. O projeto Eclipse
![]() |
O projeto Eclipse da camada [DAO] será o seguinte:
![]() |
- o pacote [elections.dao.entities] contém os objetos manipulados pela camada [DAO];
- o pacote [elections.dao.service] contém a implementação da camada [DAO];
- o pacote [elections.dao.config] contém a configuração da camada [DAO]
- o pacote [elections.dao.junit] contém uma classe de teste JUnit do projeto;
- o pacote [elections.dao.console] contém uma classe executável de teste;
7.4. Configuração do projeto Maven
![]() |
O arquivo [pom.xml] que configura o projeto Maven é o seguinte:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-dao-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<!-- biblioteca jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Teste do Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Logging do Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plug-ins -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>config.AppConfig</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- para a instalação do artefato do projeto no repositório local do Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Este arquivo é semelhante ao descrito no parágrafo 6.5.1. Foram feitas as seguintes alterações:
- linhas 8-12: foi definido um projeto Maven pai. O projeto [spring-boot-starter-parent] (linha 10) define um grande número de dependências com suas respectivas versões. Ao utilizar uma delas (linhas 19-57), não é necessário especificar a versão, uma vez que esta já está definida no projeto Maven pai;
- linhas 40-51: dependências necessárias para a classe de teste do projeto. Essas dependências possuem o atributo [<scope>test</scope>], o que significa que são necessárias apenas para as classes da pasta [src / test / java]. Essas dependências não serão incluídas no arquivo final do projeto;
- linhas 53-56: a biblioteca [spring-boot-starter-logging] será usada pelo Spring para gerar logs na tela;
- linhas 14-17: propriedades de configuração do Maven:
- linha 15: indica que os arquivos-fonte estão codificados em UTF-8;
- linha 16: indica que o compilador a ser utilizado deve ser da versão 1.8;
7.5. As entidades da camada [DAO]
![]() |
- [ElectionsConfig] é o modelo de objeto associado a uma linha da tabela [CONF];
- [ListeElectorale] é o modelo de objeto associado a uma linha da tabela [LISTES];
- [AbstactEntity] é a classe pai das duas classes anteriores. Ela agrupa os campos [id, version] comuns às duas classes;
- [ElectionsException] é uma classe de exceção;
7.5.1. A classe [ElectionsException]
![]() |
A classe [ElectionsException] foi descrita no parágrafo 4.3. Repetimos seu código:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// classe de exceção para o aplicativo Eleições
// a exceção é não controlada
public class ElectionsException extends RuntimeException implements Serializable {
// série ID
private static final long serialVersionUID = 1L;
// campos locais
private int code;
private List<String> erreurs;
// construtores
public ElectionsException() {
super();
}
public ElectionsException(int code, Throwable e) {
// pai
super(e);
// local
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message, Throwable e) {
// pai
super(message,e);
// local
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message) {
// pai
super(message);
// local
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public ElectionsException(int code, List<String> erreurs) {
// pai
super();
// local
this.code = code;
this.erreurs = erreurs;
}
// lista de mensagens de erro de uma exceção
private List<String> getErreursForException(Throwable th) {
// recupera-se a lista de mensagens de erro da exceção
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// recupera-se a mensagem somente se ela != null e não estiver vazia
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// causa a seguir
cause = cause.getCause();
}
return erreurs;
}
// getters e setters
...
}
Um objeto [ElectionsException] é caracterizado por duas informações:
- linha 16: um código de erro;
- linha 17: uma lista de mensagens de erro associadas à pilha de exceções que ocorreram;
- a classe possui 5 construtores (linhas 20, 24, 32, 40, 50);
- linhas 59-76: o método [getErreursForException] permite recuperar as mensagens de erro da pilha de exceções;
7.5.2. A classe [AbstractEntity]
![]() |
A classe [AbstractEntity] é a seguinte:
package elections.dao.entities;
import java.io.Serializable;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public abstract class AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
// campos
protected Long id;
protected Long version;
// construtores
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// assinatura
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// getters e setters
...
}
Ela armazena os campos [ID, NOM] das linhas das tabelas [CONF] e [LISTES] (linhas 8-9).
- linha 8: a classe possui o atributo [Abstract], indicando que não pode ser instanciada. Ela só pode ser derivada;
- linhas 26-33: assinatura jSON do objeto;
- linha 28: retorna-se a cadeia jSON de [this]. Se, no momento da execução, [this] representar um objeto derivado de [AbstactEntity], é a cadeia jSON do objeto derivado que é obtida. Assim, as classes derivadas não precisarão definir um método [toString]. O método da classe pai é suficiente;
7.5.3. A classe [ElectionsConfig]
![]() |
A classe [ElectionsConfig] é a seguinte:
package elections.dao.entities;
public class ElectionsConfig extends AbstractEntity {
private static final long serialVersionUID = 1L;
// campos
private int nbSiegesAPourvoir;
private double seuilElectoral;
// construtores
public ElectionsConfig() {
}
public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
// pai
super(id, version);
// campos locais
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// getters e setters
...
}
- linha 4: a classe estende a classe [AbstractEntity];
- linhas 8-9: armazenam as informações das colunas [SAP, SEUILELECTORAL] da tabela [CONF];
7.5.4. A classe [ListeElectorale]
![]() |
A classe [ListeElectorale] é a seguinte:
package elections.dao.entities;
public class ListeElectorale extends AbstractEntity {
// campos
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// construtores
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
// pai
super();
// campos locais
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
public ListeElectorale(Long id, Long version, String nom, int voix, int sieges, boolean elimine) {
// pai
super(id, version);
// campos locais
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
// métodos privados
private void initNom(String nom) {
this.nom = nom.trim();
if ("".equals(nom)) {
throw new ElectionsException(10, "Le nom ne peut être vide");
}
}
private void initVoix(int voix) {
this.voix = voix;
if (voix < 0) {
throw new ElectionsException(11, "Le nombre de voix ne peut être <0");
}
}
private void initSieges(int sieges) {
this.sieges = sieges;
if (sieges < 0) {
throw new ElectionsException(12, "Le nombre de sièges ne peut être <0");
}
}
// getters e setters
public String getNom() {
return nom;
}
public void setNom(String nom) {
initNom(nom);
}
public int getVoix() {
return voix;
}
public void setVoix(int voix) {
initVoix(voix);
}
public int getSieges() {
return sieges;
}
public void setSieges(int sieges) {
initSieges(sieges);
}
public boolean isElimine() {
return elimine;
}
public void setElimine(boolean elimine) {
this.elimine = elimine;
}
}
- linha 3: a classe estende a classe [AbstractEntity];
- linhas 6-9: a classe armazena as colunas [NOM, VOIX, SIEGES, ELIMINE] da tabela [LISTES];
7.6. Configuração Spring da camada [DAO]
![]() |
![]() |
A classe [AppConfig] é uma classe de configuração do Spring que configura o acesso ao banco de dados da seguinte maneira:
package elections.dao.config;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan(basePackages = { "elections.dao.service" })
@EnableCaching
public class AppConfig {
// constantes
public final static String URL = "jdbc:mysql://localhost:3306/dbelections";
public final static String USER = "root";
public final static String PASSWD = "";
public final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
public final static String SELECT_LISTES = "SELECT ID, VERSION, NOM, VOIX, SIEGES, ELIMINE FROM LISTES";
public final static String SELECT_CONF = "SELECT ID, VERSION, SAP, SEUILELECTORAL, SAP FROM CONF";
public final static String UPDATE_LISTES = "UPDATE LISTES SET VOIX=?, SIEGES=?, ELIMINE=? WHERE ID=?";
@Bean
public DataSource dataSource() {
// fonte de dados TomcatJdbc
DataSource dataSource = new DataSource();
// configuração de acesso JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// uma conexão inicialmente aberta
dataSource.setInitialSize(1);
// resultado
return dataSource;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- linhas 25-38: o acesso à BD será feito por meio de uma fonte de dados [tomcat-jdbc]. Esse tipo de fonte foi utilizado e explicado no parágrafo 6.5;
- linhas 17-23: um conjunto de constantes estáticas acessíveis por todas as classes do projeto;
- linha 13: a anotação [@Configuration] transforma a classe [AppConfig] em uma classe de configuração do Spring;
- linha 11: a anotação [@ComponentScan] indica os pacotes onde é possível encontrar objetos Spring. Aqui, vamos definir um objeto Spring no pacote [dao]. A anotação [@ComponentScan] transforma a classe em uma classe de configuração, o que nos poupa de ter que colocar a anotação [@Configuration];
- a linha 12 ativa o gerenciamento de um cache. O princípio é o seguinte:
- colocamos a anotação [@Cacheable('nom_du_cache')] em um método M. O 'nome_do-cache' é o nome utilizado na linha 41;
- quando o método M é chamado pela primeira vez, seus resultados são retornados e também armazenados no cache nomeado pela anotação;
- quando o método M é chamado nas vezes seguintes com os mesmos parâmetros da primeira vez, ele não é executado e o Spring se limita a retornar os valores armazenados no cache;
7.7. Configuração dos logs
![]() |
As bibliotecas de logs são definidas pela seguinte dependência em [pom.xml]:
<!-- Registro do Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Essa dependência traz as seguintes bibliotecas:
![]() |
A biblioteca [logback] é responsável pelos logs. Ela é configurada por dois arquivos:
- [logback.xml] para o ramo principal do código;
- [logback-test.xml] para o ramo de teste do código. Na ausência desse arquivo, o arquivo anterior é utilizado;
Esses dois arquivos devem estar localizados no diretório [Classpath] do projeto. Por esse motivo, eles são colocados na pasta:
- [src / main / ressources] para o ramo principal do código;
- [src / test / ressources] para o ramo de teste do código;
O conteúdo dos arquivos é o mesmo:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- os codificadores recebem, por padrão, o tipo
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- controle do nível dos logs -->
<root level="info"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Tudo acontece na linha 12, onde se define o nível de informação desejado:
- [debug]: o nível mais detalhado;
- [off]: sem logs;
- [info]: nível normal de logs;
- [warn]: igual ao nível [info], mais as mensagens de aviso (warning). Essas mensagens indicam a possibilidade de um erro;
Mude para o modo [debug] assim que surgirem erros durante a execução.
7.8. Implementação da camada [DAO]
![]() |
![]() |
A interface [IDao] da camada [DAO] é a seguinte:
package istia.st.elections.webapi.client.dao;
import istia.st.elections.webapi.client.entities.ElectionsConfig;
import istia.st.elections.webapi.client.entities.ListeElectorale;
public interface IDao {
// configuração das eleições
public ElectionsConfig getElectionsConfig();
// as listas concorrentes
public ListeElectorale[] getListesElectorales();
// salvamento dos resultados da eleição
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
O esboço da classe [ElectionsDaoJdbc] que implementa a camada [dao] com um banco de dados será o seguinte:
package elections.dao.service;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.dao.entities.ListeElectorale;
@Component
@SuppressWarnings("unused")
public class ElectionDaoJdbc implements IElectionsDao {
@Autowired
private DataSource dataSource;
@Cacheable("electionsConfig")
// obtenção da configuração da eleição
public ElectionsConfig getElectionsConfig() {
throw new RuntimeException("[getElectionsConfig] not yet implemented");
}
// obtenção das listas
public ListeElectorale[] getListesElectorales() {
throw new RuntimeException("[getListesElectorales] not yet implemented");
}
// alteração das listas [voix, sieges, elimine]
public void setListesElectorales(ListeElectorale[] listesElectorales) {
throw new RuntimeException("[setListesElectorales] not yet implemented");
}
// -------------------- métodos privados
// gerenciamento do finally
private ElectionsException doFinally(int code, ResultSet rs, PreparedStatement ps, Connection connexion) {
// fechamento ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// fechamento [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// encerrar a conexão
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// retorna um [ElectionsException]
return new ElectionsException(code, e3);
}
}
// sem exceção
return null;
}
// gestão do catch
private ElectionsException doCatchException(int code1, int code2, Connection connexion, Throwable th) {
// é gerado um [ElectionsException]
ElectionsException ex1 = new ElectionsException(code1, th);
// cancelamento da transação
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
}
// retorna a exceção
return ex1;
}
}
- linha 24: a anotação [@Cacheable] é uma anotação do Spring que solicita que os resultados do método [getElectionsConfig] sejam armazenados em memória (“armazenados em cache”). É possível fazer isso aqui porque o conteúdo da tabela [CONF] nunca muda. Não seria possível aplicar essa anotação ao método [getListesElectorales], pois o conteúdo da tabela [LISTES] muda ao longo do tempo;
- os métodos [doCatchException] e [doFinally] retornam um tipo [ElectionsException]. O método [doFinally] retorna um ponteiro null se a liberação dos recursos ocorreu sem erros;
Tarefa: escreva a classe [ElectionDaoJdbc] inspirando-se na classe [IntroJdbc02] estudada no parágrafo 6.5.2.
7.9. A classe de teste [Main]
![]() |
![]() |
A classe [Main] é a seguinte:
package elections.dao.console;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import elections.dao.config.AppConfig;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.dao.entities.ListeElectorale;
import elections.dao.service.IElectionsDao;
public class Main {
// fonte de dados
private static IElectionsDao dao;
public static void main(String[] args) {
// recupera-se a referência da camada [DAO] após instanciar o contexto Spring
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
// recuperação da fonte de dados
dao = ctx.getBean(IElectionsDao.class);
} catch (Exception e) {
System.out.println("Les erreurs suivantes se sont produites lors de l'initialisation du contexte Spring -------");
for (String erreur : getErreursForThrowable(e)) {
System.out.println(erreur);
}
// fim
return;
}
// leitura da BD
ElectionsConfig electionsConfig = null;
ListeElectorale[] listes;
try {
// conteúdo das duas tabelas
electionsConfig = dao.getElectionsConfig();
listes = dao.getListesElectorales();
} catch (ElectionsException e) {
System.out.println("Les erreurs suivantes se sont produites lors de la lecture des tables ----------");
for (String erreur : e.getErreurs()) {
System.out.println(erreur);
}
// fim
return;
}
// tudo correu bem — exibição
System.out.println(String.format("Nombre de sièges à pourvoir : %d", electionsConfig.getNbSiegesAPourvoir()));
System.out.println(String.format("Seuil électoral : %5.2f", electionsConfig.getSeuilElectoral()));
System.out.println("Listes candidates----------------");
for (ListeElectorale liste : listes) {
System.out.println(liste);
}
}
// métodos privados ------------------
private static List<String> getErreursForThrowable(Throwable th) {
// recuperamos a lista de mensagens de erro da exceção
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// recuperamos a mensagem somente se ela for !=null e não estiver vazia
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// próxima causa
cause = cause.getCause();
}
return erreurs;
}
}
- linhas 21-31: recuperação de uma referência na camada [DAO];
- linha 21: utiliza-se uma sintaxe chamada try_with_resources. Sua sintaxe é a seguinte:
try (T ressource=expression) {
// exploração de recurso
...
}
- (continuação)
- o tipo T da linha 1 deve implementar a interface [java.lang.AutoCloseable];
- ao sair do bloco das linhas 1 a 4, o recurso do tipo [java.lang.AutoCloseable] é automaticamente liberado, independentemente de ter ocorrido uma exceção ou não. Quem conhece a linguagem C# reconhecerá aqui um equivalente sintático e funcional da cláusula using (T recurso = expressão);
- linhas 40-49: uso da camada [DAO] para obter o conteúdo das tabelas [CONF] e [LISTES];
- linhas 41-47: testa-se o cache [electionsconfig]. Este foi definido em dois locais:
- na classe [ElectionsDaoJdbc]:
@Cacheable("electionsConfig")
public ElectionsConfig getElectionsConfig() {
- (continuação)
- na classe de configuração [AppConfig]:
@EnableCaching
public class AppConfig {
...
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- linha 59: fechamento do contexto Spring;
- linhas 62-67: exibição das informações obtidas;
Os resultados obtidos com uma camada [DAO] implementada são os seguintes:
- linhas 2-4: mostram a influência do cache:
- a consulta 1 leva 80 milissegundos;
- a consulta 2 leva 1 milissegundo;
Quando o banco de dados está desativado, obtêm-se os seguintes resultados:
7.10. Testes JUnit da classe [ElectionsDaoJdbc]
![]() |
![]() |
A classe [Test01] é a classe de teste seguinte à [JUnit]:
package elections.dao.junit;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import elections.dao.config.AppConfig;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ListeElectorale;
import elections.dao.service.IElectionsDao;
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {
// camada [DAO]
@Autowired
private IElectionsDao electionsDao;
@Before
public void init() {
// limpa-se a tabela [LISTES]
// listas concorrentes
ListeElectorale[] listes = electionsDao.getListesElectorales();
// zeramos os votos e os assentos e definimos como “false”
int voix = 0;
int sièges = 0;
boolean elimine = false;
for (ListeElectorale liste : listes) {
liste.setVoix(voix);
liste.setSieges(sièges);
liste.setElimine(elimine);
}
// tornamos esses dados persistentes por meio da camada [dao]
electionsDao.setListesElectorales(listes);
}
@Test
public void testElections01() {
...
}
}
- linha 17: a anotação [RunWith], que é uma anotação [JUnit] (linha 6), garante a integração com o Spring por meio da classe [SpringJUnit4ClassRunner];
- linha 16: a anotação [SpringApplicationConfiguration] é uma anotação [Spring] (linha 8) que permite designar as classes de configuração do teste JUnit. Aqui, designa-se a classe [AppConfig] utilizada para configurar o projeto. Assim, dispõe-se de todos os objetos Spring definidos por essa classe de configuração. É dessa forma que se pode injetar, nas linhas 21-22, uma referência na camada [DAO] que será testada;
- linha 25: a anotação [Before] indica um método que deve ser executado antes de cada teste;
- linhas 26-41: o método [init] zera os votos e assentos das listas da tabela [LISTES] e define o booleano [elimine] como [false];
O único método de teste é o seguinte:
@Test
public void testElections01() {
System.out.println("testElections01-------------------------------------");
// recuperação da configuração das eleições
ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
double seuilElectoral = electionsConfig.getSeuilElectoral();
Assert.assertEquals(6, nbSiegesAPourvoir);
Assert.assertEquals(0.05, seuilElectoral, 1E-6);
// listas em disputa
ListeElectorale[] listes = electionsDao.getListesElectorales();
// exibição dos valores lidos
System.out.println("Nombre de sièges à pourvoir : " + nbSiegesAPourvoir);
System.out.println("Seuil électoral : " + seuilElectoral);
System.out.println("Listes en compétition ---------------------");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
// atribuição de votos e cadeiras às listas
int voix = 0;
int sièges = 0;
boolean elimine = false;
for (ListeElectorale liste : listes) {
liste.setVoix(voix);
liste.setSieges(sièges);
liste.setElimine(elimine);
voix += 10;
sièges += 1;
elimine = !elimine;
}
// esses dados são tornados persistentes por meio da camada [dao]
electionsDao.setListesElectorales(listes);
// releitura dos dados
ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
// verifica-se os dados lidos
Assert.assertEquals(7, listesElectorales2.length);
voix = 0;
sièges = 0;
elimine = false;
for (ListeElectorale liste : listesElectorales2) {
Assert.assertEquals(voix, liste.getVoix());
Assert.assertEquals(sièges, liste.getSieges());
Assert.assertEquals(elimine, liste.isElimine());
voix += 10;
sièges += 1;
elimine = !elimine;
}
System.out.println("Listes en compétition ---------------------");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
}
- linhas 5-9: verifica-se se é possível recuperar o conteúdo da tabela [CONF];
- linhas 11-19: exibe-se o conteúdo da tabela [LISTES]. Não há testes aqui, apenas uma verificação visual;
- linhas 21-35: modificamos no banco de dados a tabela [LISTES], atribuindo às listas os valores para seus campos [voix, sieges, elimine];
- linhas 37-51: relê-se a tabela [LISTES] e verifica-se se o resultado obtido é realmente igual ao que foi inserido;
- linhas 52-55: verificação visual;
Os resultados na console obtidos com uma camada [DAO] implementada são os seguintes:
- linhas 1-23: logs do Spring Test;
- linhas 25-26: o conteúdo da tabela [CONF];
- linhas 27-34: o conteúdo inicial da tabela [LISTES];
- linhas 35-42: o conteúdo da tabela [LISTES] após a atribuição de valores aos campos [voix, sieges, elimine];
Além disso, o teste JUnit foi bem-sucedido:
![]() |
7.11. Criação do arquivo [with-dependencies] a partir da camada [dao]
O projeto final possui a seguinte arquitetura:
![]() |
Vale lembrar a configuração do Maven do projeto:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-dao-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<!-- biblioteca jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Teste do Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Logging do Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plug-ins -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>config.AppConfig</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- para a instalação do artefato do projeto no repositório local do Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
![]() |
Vamos gerar um único arquivo JAR que conterá as classes de todos os JARs acima, além das do projeto da camada [DAO]. Isso é feito por meio de uma modificação no arquivo [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.2.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
...
</dependencies>
<!-- plug-ins -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>console.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
- linhas 25-37: configuram um plugin do Maven para gerar o JAR;
- linha 15: indica a versão do Java a ser usada para a compilação;
Depois de fazer essa alteração, é possível gerar o arquivo jar da seguinte maneira [1-10]:
![]() |
![]() |
- em [5], selecione a pasta do projeto usando o botão [6];
- em [7], atribuir um nome à configuração de execução;
- em [8], insira a lista de tarefas do Maven a serem executadas:
- [clean]: esvazia a pasta [target] do projeto, na qual será colocado o arquivo JAR gerado;
- [compile]: compila o projeto;
- [assembly:single]: gera um único arquivo JAR contendo todas as classes do projeto e de suas dependências;
![]() |
- no [9-10], verifique se você possui um JDK (Java Development Kit) e não um JRE (Java Runtime Environment). A diferença é que o JDK possui um compilador, ao passo que o JRE não possui. Se você não tiver o JDK, será necessário adicioná-lo ao [10] junto com o [11]. Para isso, siga o procedimento descrito no parágrafo 3.1;
![]() |
- no [17], execute a configuração de execução;
- em [13], o arquivo compactado gerado;
É possível abrir esse arquivo compactado com um programa de descompactação:
![]() |
7.12. Teste do arquivo da camada [DAO]
Vamos criar um projeto Eclipse padrão (não Maven) [1]:
![]() |
Vamos copiar e colar o pacote [dao.console] do projeto [elections-dao-jdbc-01] no projeto [elections-dao-jdbc-02] [2]. Aparecem vários erros porque a classe [Main] faz referência a classes que não estão em seu [Classpath]. Vamos modificar este último.
Primeiro, criamos a pasta [lib] no novo projeto:
![]() |
![]() |
No [9], colocamos o arquivo criado na etapa anterior na pasta [lib] e, em seguida, alteramos o Build Path do projeto:
![]() |
![]() |
![]() |
- para [18]; importamos o arquivo da camada [DAO] criado anteriormente;
![]() |
- no [19], o projeto não apresenta mais erros;
É possível, então, executar a classe [Main]. Obtêm-se os mesmos resultados que anteriormente.







































