7. [TD]: Implementação da camada [DAO] do TD utilizando a API JDBC
Palavras-chave: bases de dados relacionais, API JDBC, SQLException.
Vamos rever a arquitetura em camadas da nossa aplicação:
![]() |
Os dados necessários para a eleição estão armazenados numa base de dados MySQL [dbelections]
7.1. Suporte
![]() |
A pasta [support / chap-07] [1] contém:
- os projetos Eclipse para este capítulo [2];
- o script SQL para criar a base de dados MySQL [dbelections] [3];
7.2. A base de dados [ dbelections]
Tarefa: Crie a base de dados MySQL [dbelections] seguindo o procedimento descrito na secção 6.4.2.
A base de dados [dbelections] é uma base de dados MySQL com duas tabelas:
![]() |
A tabela [conf] contém as informações de configuração da eleição:
![]() |
- [id]: chave primária autoincremental;
- [version]: número da versão do registo — pode ser ignorado aqui;
- [sap]: número de lugares a preencher;
- [votingThreshold]: o limiar abaixo do qual uma lista é eliminada;
O seu conteúdo é o seguinte:
![]() |
A tabela [listes] contém as listas de candidatos para a eleição:
![]() |
- [id]: chave primária autoincremental;
- [version]: número da versão do registo — pode ser ignorado aqui;
- [name]: nome da lista;
- [votes]: votos para a lista — só são conhecidos após a entrada do utilizador na camada [presentation];
- [seats]: número de lugares conquistados — conhecido apenas após o cálculo pela camada [business];
- [eliminated]: 1 se a lista for eliminada, 0 caso contrário — conhecido apenas após o cálculo da camada [business];
O conteúdo da tabela [listes] é o seguinte:
![]() |
O script SQL para gerar a base de dados [dbelections] chama-se [dbelections.sql] e encontra-se no servidor. O seu código é o seguinte:
-- phpMyAdmin SQL Dump
-- version 4.0.4
-- http://www.phpmyadmin.net
--
-- Customer: localhost
-- Generated on: Wed March 11, 2015 at 12:20 pm
-- Server version: 5.6.12-log
-- Version of 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 */;
--
-- Database: `dbelections`
--
CREATE DATABASE IF NOT EXISTS `dbelections` DEFAULT CHARACTER SET utf8 COLLATE utf8_swedish_ci;
USE `dbelections`;
-- --------------------------------------------------------
--
-- Structure of the `conf` table
--
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 ;
--
-- Contents of the `conf` table
--
INSERT INTO `conf` (`id`, `version`, `sap`, `seuilelectoral`) VALUES
(1, 1, 6, 0.05);
-- --------------------------------------------------------
--
-- Structure of the `lists` table
--
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 ;
--
-- Contents of the `lists` table
--
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 para a 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 para o projeto;
- o pacote [elections.dao.console] contém uma classe de teste executável;
7.4. Configuração do projeto Maven
![]() |
O ficheiro [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>
<!-- library 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>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plugins -->
<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>
<!-- to install the project artifact in the local Maven repository -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Este ficheiro é semelhante ao descrito na Secção 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, juntamente com as suas versões. Ao utilizar uma delas (linhas 19–57), não é necessário especificar a versão, uma vez que esta está definida no projeto Maven pai;
- linhas 40–51: dependências necessárias para a classe de teste do projeto. Estas dependências têm o atributo [<scope>test</scope>], o que significa que são necessárias apenas para as classes na pasta [src/test/java]. Estas dependências não serão incluídas no arquivo final do projeto;
- linhas 53–56: a biblioteca [spring-boot-starter-logging] será utilizada pelo Spring para registar mensagens na consola;
- linhas 14–17: propriedades de configuração do Maven:
- linha 15: especifica que os ficheiros de código-fonte estão codificados em UTF-8;
- linha 16: especifica que a versão do compilador deve ser 1.8;
7.5. Entidades na camada [DAO]
![]() |
- [ElectionsConfig] é o modelo de objeto associado a uma linha na tabela [CONF];
- [VoterList] é o modelo de objeto associado a uma linha na tabela [LISTS];
- [AbstactEntity] é a classe pai das duas classes anteriores. Encapsula os campos [id, version] comuns a ambas as classes;
- [ElectionsException] é uma classe de exceção;
7.5.1. A classe [ElectionsException]
![]() |
A classe [ElectionsException] foi descrita na Secção 4.3. Aqui está novamente o seu código:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// exception class for the Elections application
// the exception is uncontrolled
public class ElectionsException extends RuntimeException implements Serializable {
// serial ID
private static final long serialVersionUID = 1L;
// local fields
private int code;
private List<String> erreurs;
// manufacturers
public ElectionsException() {
super();
}
public ElectionsException(int code, Throwable e) {
// parent
super(e);
// local
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message, Throwable e) {
// parent
super(message,e);
// local
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message) {
// parent
super(message);
// local
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public ElectionsException(int code, List<String> erreurs) {
// parent
super();
// local
this.code = code;
this.erreurs = erreurs;
}
// list of exception error messages
private List<String> getErreursForException(Throwable th) {
// retrieve the list of exception error messages
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// the message is retrieved only if it is !=null and not blank
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// next cause
cause = cause.getCause();
}
return erreurs;
}
// getters and 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 ao rastreio da pilha das exceções que ocorreram;
- a classe tem 5 construtores (linhas 20, 24, 32, 40, 50);
- linhas 59–76: o método [getErrorsForException] recupera 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;
// fields
protected Long id;
protected Long version;
// manufacturers
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// signature
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// getters and setters
...
}
Armazena os campos [ID, NAME] das linhas nas tabelas [CONF] e [LISTS] (linhas 8–9).
- linha 8: a classe tem o atributo [Abstract], indicando que não pode ser instanciada. Só pode ser derivada;
- linhas 26–33: assinatura JSON do objeto;
- linha 28: a cadeia JSON de [this] é devolvida. Se, em tempo de execução, [this] representar um objeto derivado de [AbstractEntity], obtém-se a cadeia JSON do objeto derivado. As classes derivadas não precisarão, assim, de definir um método [toString]. O 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;
// fields
private int nbSiegesAPourvoir;
private double seuilElectoral;
// manufacturers
public ElectionsConfig() {
}
public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
// parent
super(id, version);
// local fields
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// getters and 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 [VoterList]
![]() |
A classe [VoterList] é a seguinte:
package elections.dao.entities;
public class ListeElectorale extends AbstractEntity {
// fields
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// manufacturers
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
// parent
super();
// local fields
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
public ListeElectorale(Long id, Long version, String nom, int voix, int sieges, boolean elimine) {
// parent
super(id, version);
// local fields
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
// private methods
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 and 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 [NAME, VOTES, SEATS, ELIMINATED] da tabela [LISTS];
7.6. Configuração Spring da camada [DAO]
![]() |
![]() |
A classe [AppConfig] é uma classe de configuração do Spring que configura o acesso à base de dados da seguinte forma:
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 {
// constants
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() {
// data source TomcatJdbc
DataSource dataSource = new DataSource();
// configuration access JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// an initially open connection
dataSource.setInitialSize(1);
// result
return dataSource;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- linhas 25–38: O acesso à base de dados será feito através de uma fonte de dados [tomcat-jdbc]. Este tipo de fonte de dados foi utilizado e explicado na secção 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] torna a classe [AppConfig] uma classe de configuração Spring;
- linha 11: a anotação [@ComponentScan] especifica os pacotes onde os objetos Spring podem ser encontrados. Aqui, definiremos um objeto Spring no pacote [dao]. A anotação [@ComponentScan] torna a classe uma classe de configuração, poupando-nos de ter de adicionar a anotação [@Configuration];
- A linha 12 ativa a gestão da cache. O princípio é o seguinte:
- aplicamos a anotação [@Cacheable('cache_name')] a um método M. O 'cache_name' é o nome utilizado na linha 41;
- quando o método M é chamado pela primeira vez, os seus resultados são devolvidos e também armazenados no cache indicado pela anotação;
- quando o método M é chamado posteriormente com os mesmos parâmetros da primeira vez, ele não é executado, e o Spring simplesmente retorna os valores armazenados no cache;
7.7. Configuração de registo
![]() |
As bibliotecas de registo são definidas pela seguinte dependência no ficheiro [pom.xml]:
<!-- Spring Boot Logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Esta dependência inclui as seguintes bibliotecas:
![]() |
A biblioteca [logback] gere o registo. É configurada por dois ficheiros:
- [logback.xml] para o ramo principal do código;
- [logback-test.xml] para o ramo de teste do código. Se este ficheiro estiver em falta, é utilizado o ficheiro anterior;
Estes dois ficheiros devem estar localizados no [Classpath] do projeto. Por este motivo, são colocados na pasta:
- [src/main/resources] para o ramo principal do código;
- [src/test/resources] para o ramo de teste do código;
O conteúdo dos ficheiros é o mesmo aqui:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are by default assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- log level control -->
<root level="info"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Tudo acontece na linha 12, onde definimos o nível de registo pretendido:
- [debug]: o nível mais detalhado;
- [off]: sem registos;
- [info]: o nível normal de registo;
- [warn]: igual a [info], mas com mensagens de aviso. Estas mensagens indicam um erro potencial;
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 {
// election configuration
public ElectionsConfig getElectionsConfig();
// competing lists
public ListeElectorale[] getListesElectorales();
// save election results
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
O esqueleto da classe [ElectionsDaoJdbc] que implementa a camada [dao] com uma base 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")
// obtaining conf of the election
public ElectionsConfig getElectionsConfig() {
throw new RuntimeException("[getElectionsConfig] not yet implemented");
}
// obtaining lists
public ListeElectorale[] getListesElectorales() {
throw new RuntimeException("[getListesElectorales] not yet implemented");
}
// list modification [votes, seats, eliminated]
public void setListesElectorales(ListeElectorale[] listesElectorales) {
throw new RuntimeException("[setListesElectorales] not yet implemented");
}
// -------------------- private methods
// end-of-life management
private ElectionsException doFinally(int code, ResultSet rs, PreparedStatement ps, Connection connexion) {
// closure ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// closure [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// close connection
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// returns a [ElectionsException]
return new ElectionsException(code, e3);
}
}
// no exceptions
return null;
}
// wrestling management
private ElectionsException doCatchException(int code1, int code2, Connection connexion, Throwable th) {
// we generate a [ElectionsException]
ElectionsException ex1 = new ElectionsException(code1, th);
// cancel transaction
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
}
// we return the exception
return ex1;
}
}
- linha 24: a anotação [@Cacheable] é uma anotação Spring que instrui o sistema a armazenar em cache os resultados do método [getElectionsConfig]. Isto é possível aqui porque o conteúdo da tabela [CONF] nunca muda. Não seria possível aplicar esta anotação ao método [getListesElectorales] porque o conteúdo da tabela [LISTES] muda ao longo do tempo;
- Os métodos [doCatchException] e [doFinally] devolvem um objeto do tipo [ElectionsException]. O método [doFinally] devolve um ponteiro nulo se os recursos tiverem sido libertados sem erros;
Tarefa: Escreva a classe [ElectionDaoJdbc], inspirando-se na classe [IntroJdbc02] estudada na Secção 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 {
// data source
private static IElectionsDao dao;
public static void main(String[] args) {
// retrieve the [DAO] layer reference after instantiating the Spring context
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
// data source recovery
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);
}
// end
return;
}
// reading the BD
ElectionsConfig electionsConfig = null;
ListeElectorale[] listes;
try {
// contents of both tables
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);
}
// end
return;
}
// all went well - display
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);
}
}
// private methods ------------------
private static List<String> getErreursForThrowable(Throwable th) {
// retrieve the list of exception error messages
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// the message is retrieved only if it is !=null and not blank
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// next cause
cause = cause.getCause();
}
return erreurs;
}
}
- linhas 21–31: recuperação de uma referência à camada [DAO];
- linha 21: utilizamos uma sintaxe chamada try_with_resources. A sua sintaxe é a seguinte:
try (T ressource=expression) {
// exploitation de ressource
...
}
- (continuação)
- O tipo T na linha 1 deve implementar a interface [java.lang.AutoCloseable];
- Ao sair do bloco das linhas 1–4, o recurso do tipo [java.lang.AutoCloseable] é automaticamente libertado, independentemente de ter ocorrido uma exceção. Quem estiver familiarizado com a linguagem C# reconhecerá isto como uma contraparte sintática e funcional da cláusula using (T resource = expression);
- Linhas 40–49: Utilização da camada [DAO] para recuperar o conteúdo das tabelas [CONF] e [LISTES];
- linhas 41-47: o cache [electionsconfig] é testado. Este cache 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: encerramento do contexto Spring;
- linhas 62–67: exibição das informações recuperadas;
Os resultados obtidos com uma camada [DAO] implementada são os seguintes:
- Linhas 2–4: mostram o impacto da cache:
- A solicitação 1 demora 80 milissegundos;
- A consulta 2 demora 1 milissegundo;
Quando a base de dados está offline, obtêm-se os seguintes resultados:
7.10. Testes JUnit para a classe [ ElectionsDaoJdbc]
![]() |
![]() |
A classe [Test01] é a seguinte classe de teste [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 {
// layer [DAO]
@Autowired
private IElectionsDao electionsDao;
@Before
public void init() {
// the table is cleaned [LISTES]
// competing lists
ListeElectorale[] listes = electionsDao.getListesElectorales();
// set votes and seats to 0 and eliminate 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);
}
// we make this data persistent using the [dao] layer
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 através da classe [SpringJUnit4ClassRunner];
- linha 16: a anotação [SpringApplicationConfiguration] é uma anotação [Spring] (linha 8) que permite especificar as classes de configuração para o teste JUnit. Aqui, especificamos a classe [AppConfig] utilizada para configurar o projeto. Temos então acesso a todos os objetos Spring definidos por esta classe de configuração. É assim que podemos injetar, nas linhas 21–22, uma referência à 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] define os votos e lugares na tabela [LISTES] como zero e o booleano [elimine] como [false];
O único método de teste é o seguinte:
@Test
public void testElections01() {
System.out.println("testElections01-------------------------------------");
// election configuration recovery
ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
double seuilElectoral = electionsConfig.getSeuilElectoral();
Assert.assertEquals(6, nbSiegesAPourvoir);
Assert.assertEquals(0.05, seuilElectoral, 1E-6);
// competing lists
ListeElectorale[] listes = electionsDao.getListesElectorales();
// display read values
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]);
}
// votes and seats are allocated to lists
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;
}
// we make this data persistent using the [dao] layer
electionsDao.setListesElectorales(listes);
// data re-reading
ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
// check data read
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: garantimos que conseguimos recuperar o conteúdo da tabela [CONF];
- linhas 11–19: exibimos o conteúdo da tabela [LISTES]. Não há testes aqui, apenas uma verificação visual;
- linhas 21–35: modificamos a tabela [LISTES] na base de dados, atribuindo valores aos campos das listas [voices, seats, eliminated];
- linhas 37–51: lemos novamente a tabela [LISTES] e verificamos se o resultado corresponde ao que foi introduzido;
- linhas 52–55: verificação visual;
Os resultados da consola obtidos com uma camada [DAO] implementada são os seguintes:
- linhas 1-23: registos do Teste de Primavera;
- 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 é bem-sucedido:
![]() |
7.11. Criação do arquivo [with-dependencies] para a camada [dao]
O projeto final tem a seguinte arquitetura:
![]() |
Vamos rever a configuração 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>
<!-- library 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>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plugins -->
<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>
<!-- to install the project artifact in the local Maven repository -->
<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 ficheiro JAR que conterá as classes de todos os ficheiros JAR acima referidos, bem como as do projeto da camada [DAO]. Para tal, basta efetuar uma alteração no ficheiro [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>
<!-- plugins -->
<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: especifica a versão do Java a utilizar para a compilação;
Depois de efetuar esta alteração, pode gerar o JAR da seguinte forma [1-10]:
![]() |
![]() |
- Em [5], selecione a pasta do projeto utilizando o botão [6];
- em [7], atribua um nome à configuração de compilação;
- em [8], introduza a lista de tarefas Maven a executar:
- [clean]: limpa a pasta [target] do projeto, onde o JAR gerado será colocado;
- [compile]: compila o projeto;
- [assembly:single]: gera um único ficheiro JAR contendo todas as classes do projeto e as suas dependências;
![]() |
- em [9-10], verifique se possui um JDK (Java Development Kit) e não um JRE (Java Runtime Environment). A diferença é que o JDK inclui um compilador, ao passo que o JRE não. Se não tiver um JDK, deve adicionar um em [10] utilizando [11]. Para tal, siga o procedimento descrito na secção 3.1;
![]() |
- Em [17], execute a configuração de compilação;
- em [13], o arquivo gerado;
Pode abrir este arquivo com um programa de descompactação:
![]() |
7.12. Testar o 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] para o projeto [elections-dao-jdbc-02] [2]. Surgem vários erros porque a classe [Main] faz referência a classes que não estão no seu [Classpath]. Vamos modificar o [Classpath].
Primeiro, criamos [2-8] uma pasta [lib] no novo projeto:
![]() |
![]() |
Em [9], colocamos o arquivo criado na etapa anterior na pasta [lib] e, em seguida, modificamos o Caminho de Compilação do projeto:
![]() |
![]() |
![]() |
- em [18], importámos o arquivo da camada [DAO] criada anteriormente;
![]() |
- em [19], o projeto já não apresenta erros;
Podemos então executar a classe [Main]. Obtemos os mesmos resultados que antes.







































