7. [TD]: Implementazione del livello [DAO] del TD utilizzando l'API JDBC
Parole chiave: database relazionali, API JDBC, SQLException.
Rivediamo l'architettura a livelli della nostra applicazione:
![]() |
I dati necessari per le elezioni sono memorizzati in un database MySQL [dbelections]
7.1. Assistenza
![]() |
La cartella [support / chap-07] [1] contiene:
- i progetti Eclipse relativi a questo capitolo [2];
- lo script SQL per la creazione del database MySQL [dbelections] [3];
7.2. Il database [ dbelections]
Compito: creare il database MySQL [dbelections] seguendo la procedura descritta nella sezione 6.4.2.
Il database [dbelections] è un database MySQL con due tabelle:
![]() |
La tabella [conf] contiene le informazioni relative alla configurazione delle elezioni:
![]() |
- [id]: chiave primaria autoincrementale;
- [version]: numero di versione del record — può essere ignorato in questo caso;
- [sap]: numero di posti da assegnare;
- [votingThreshold]: la soglia al di sotto della quale una lista viene eliminata;
Il suo contenuto è il seguente:
![]() |
La tabella [listes] contiene le liste dei candidati alle elezioni:
![]() |
- [id]: chiave primaria autoincrementale;
- [version]: numero di versione del record — può essere ignorato in questo caso;
- [name]: nome della lista;
- [votes]: voti per la lista — noto solo dopo l'input dell'utente nel livello [presentation];
- [seats]: numero di seggi conquistati — noto solo dopo il calcolo da parte del livello [business];
- [eliminato]: 1 se la lista è stata eliminata, 0 in caso contrario — noto solo dopo il calcolo del livello [business];
Il contenuto della tabella [listes] è il seguente:
![]() |
Lo script SQL per generare il database [dbelections] si chiama [dbelections.sql] e si trova sul server. Il codice è il seguente:
-- 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. Il progetto Eclipse
![]() |
Il progetto Eclipse per il livello [DAO] sarà il seguente:
![]() |
- il pacchetto [elections.dao.entities] contiene gli oggetti gestiti dal livello [DAO];
- il pacchetto [elections.dao.service] contiene l'implementazione del livello [DAO];
- il pacchetto [elections.dao.config] contiene la configurazione per il livello [DAO]
- il pacchetto [elections.dao.junit] contiene una classe di test JUnit per il progetto;
- il pacchetto [elections.dao.console] contiene una classe di test eseguibile;
7.4. Configurazione del progetto Maven
![]() |
Il file [pom.xml] che configura il progetto Maven è il seguente:
<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>
Questo file è simile a quello descritto nella Sezione 6.5.1. Sono state apportate le seguenti modifiche:
- righe 8–12: è stato definito un progetto Maven padre. Il progetto [spring-boot-starter-parent] (riga 10) definisce un gran numero di dipendenze insieme alle loro versioni. Quando si utilizza una di esse (righe 19–57), non è necessario specificare la versione, poiché è definita nel progetto Maven padre;
- righe 40–51: dipendenze richieste per la classe di test del progetto. Queste dipendenze hanno l’attributo [<scope>test</scope>], il che significa che sono richieste solo per le classi nella cartella [src/test/java]. Queste dipendenze non saranno incluse nell’archivio finale del progetto;
- righe 53–56: la libreria [spring-boot-starter-logging] verrà utilizzata da Spring per la registrazione nella console;
- righe 14–17: proprietà di configurazione di Maven:
- riga 15: specifica che i file sorgente sono codificati in UTF-8;
- riga 16: specifica che la versione del compilatore deve essere 1.8;
7.5. Entità nel livello [DAO]
![]() |
- [ElectionsConfig] è il modello a oggetti associato a una riga della tabella [CONF];
- [VoterList] è il modello a oggetti associato a una riga della tabella [LISTS];
- [AbstactEntity] è la classe padre delle due classi precedenti. Incapsula i campi [id, version] comuni a entrambe le classi;
- [ElectionsException] è una classe di eccezione;
7.5.1. La classe [ElectionsException]
![]() |
La classe [ElectionsException] è stata descritta nella Sezione 4.3. Ecco nuovamente il suo codice:
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
...
}
Un oggetto [ElectionsException] è caratterizzato da due informazioni:
- riga 16: un codice di errore;
- riga 17: un elenco di messaggi di errore associati alla traccia dello stack delle eccezioni verificatesi;
- la classe ha 5 costruttori (righe 20, 24, 32, 40, 50);
- righe 59–76: il metodo [getErrorsForException] recupera i messaggi di errore dallo stack delle eccezioni;
7.5.2. La classe [AbstractEntity]
![]() |
La classe [AbstractEntity] è la seguente:
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
...
}
Memorizza i campi [ID, NAME] delle righe nelle tabelle [CONF] e [LISTS] (righe 8–9).
- riga 8: la classe ha l'attributo [Abstract] che indica che non può essere istanziata. Può solo essere derivata;
- righe 26–33: firma JSON dell'oggetto;
- riga 28: viene restituita la stringa JSON di [this]. Se, in fase di esecuzione, [this] rappresenta un oggetto derivato da [AbstractEntity], si ottiene la stringa JSON dell'oggetto derivato. Le classi derivate non dovranno quindi definire un metodo [toString]. È sufficiente quello della classe padre;
7.5.3. La classe [ElectionsConfig]
![]() |
La classe [ElectionsConfig] è la seguente:
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
...
}
- riga 4: la classe estende la classe [AbstractEntity];
- righe 8-9: memorizza le informazioni dalle colonne [SAP, SEUILELECTORAL] della tabella [CONF];
7.5.4. La classe [VoterList]
![]() |
La classe [VoterList] è la seguente:
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;
}
}
- riga 3: la classe estende la classe [AbstractEntity];
- righe 6–9: la classe memorizza le colonne [NAME, VOTES, SEATS, ELIMINATED] della tabella [LISTS];
7.6. Configurazione Spring del livello [DAO]
![]() |
![]() |
La classe [AppConfig] è una classe di configurazione Spring che configura l'accesso al database come segue:
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");
}
}
- righe 25–38: L'accesso al database avverrà tramite un'origine dati [tomcat-jdbc]. Questo tipo di origine dati è stato utilizzato e spiegato nella sezione 6.5;
- righe 17–23: un insieme di costanti statiche accessibili da tutte le classi del progetto;
- riga 13: l'annotazione [@Configuration] rende la classe [AppConfig] una classe di configurazione Spring;
- riga 11: l'annotazione [@ComponentScan] specifica i pacchetti in cui si possono trovare gli oggetti Spring. Qui definiremo un oggetto Spring nel pacchetto [dao]. L'annotazione [@ComponentScan] rende la classe una classe di configurazione, evitando di dover aggiungere l'annotazione [@Configuration];
- La riga 12 abilita la gestione della cache. Il principio è il seguente:
- applichiamo l'annotazione [@Cacheable('cache_name')] a un metodo M. Il 'cache_name' è il nome utilizzato alla riga 41;
- quando il metodo M viene chiamato per la prima volta, i suoi risultati vengono restituiti e memorizzati nella cache indicata dall'annotazione;
- quando il metodo M viene richiamato successivamente con gli stessi parametri della prima volta, non viene eseguito e Spring restituisce semplicemente i valori memorizzati nella cache;
7.7. Configurazione del log
![]() |
Le librerie di logging sono definite dalla seguente dipendenza nel file [pom.xml]:
<!-- Spring Boot Logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Questa dipendenza include le seguenti librerie:
![]() |
La libreria [logback] gestisce la registrazione. È configurata da due file:
- [logback.xml] per il ramo principale del codice;
- [logback-test.xml] per il ramo di test del codice. Se questo file manca, viene utilizzato al suo posto il file precedente;
Questi due file devono trovarsi nel [Classpath] del progetto. Per questo motivo, sono collocati nella cartella:
- [src/main/resources] per il ramo principale del codice;
- [src/test/resources] per il ramo di test del codice;
Il contenuto dei file è lo stesso in entrambi i casi:
<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>
Tutto avviene alla riga 12, dove impostiamo il livello di log desiderato:
- [debug]: il livello più dettagliato;
- [off]: nessun log;
- [info]: il livello di registrazione normale;
- [warn]: come [info] più i messaggi di avviso. Questi messaggi indicano un potenziale errore;
Passare alla modalità [debug] non appena compaiono errori durante l'esecuzione.
7.8. Implementazione del livello [DAO]
![]() |
![]() |
L'interfaccia [IDao] del livello [DAO] è la seguente:
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);
}
Lo scheletro della classe [ElectionsDaoJdbc] che implementa il livello [dao] con un database sarà il seguente:
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;
}
}
- riga 24: l'annotazione [@Cacheable] è un'annotazione Spring che indica al sistema di memorizzare nella cache i risultati del metodo [getElectionsConfig]. Ciò è possibile in questo caso perché il contenuto della tabella [CONF] non cambia mai. Non sarebbe possibile applicare questa annotazione al metodo [getListesElectorales] perché il contenuto della tabella [LISTES] cambia nel tempo;
- I metodi [doCatchException] e [doFinally] restituiscono un oggetto di tipo [ElectionsException]. Il metodo [doFinally] restituisce un puntatore nullo se le risorse sono state liberate senza errori;
Compito: Scrivere la classe [ElectionDaoJdbc], ispirandosi alla classe [IntroJdbc02] studiata nella Sezione 6.5.2.
7.9. La classe di test [Main]
![]() |
![]() |
La classe [Main] è la seguente:
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;
}
}
- righe 21–31: recupero di un riferimento al livello [DAO];
- riga 21: utilizziamo una sintassi denominata `try_with_resources`. La sua sintassi è la seguente:
try (T ressource=expression) {
// exploitation de ressource
...
}
- (continua)
- Il tipo T nella riga 1 deve implementare l'interfaccia [java.lang.AutoCloseable];
- All'uscita dal blocco delle righe 1–4, la risorsa di tipo [java.lang.AutoCloseable] viene automaticamente rilasciata, indipendentemente dal fatto che si sia verificata un'eccezione. Chi ha familiarità con il linguaggio C# riconoscerà questa come una controparte sintattica e funzionale della clausola using (T resource = expression);
- Righe 40–49: utilizzo del livello [DAO] per recuperare il contenuto delle tabelle [CONF] e [LISTES];
- righe 41-47: viene testata la cache [electionsconfig]. Questa cache è stata definita in due punti:
- nella classe [ElectionsDaoJdbc]:
@Cacheable("electionsConfig")
public ElectionsConfig getElectionsConfig() {
- (continua)
- nella classe di configurazione [AppConfig]:
@EnableCaching
public class AppConfig {
...
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- riga 59: chiusura del contesto Spring;
- righe 62–67: visualizzazione delle informazioni recuperate;
I risultati ottenuti con un livello [DAO] implementato sono i seguenti:
- Righe 2–4: mostrano l'impatto della cache:
- La richiesta 1 richiede 80 millisecondi;
- La query 2 richiede 1 millisecondo;
Quando il database è offline, si ottengono i seguenti risultati:
7.10. Test JUnit per la classe [ ElectionsDaoJdbc]
![]() |
![]() |
La classe [Test01] è la seguente classe di test [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() {
...
}
}
- riga 17: l'annotazione [RunWith], che è un'annotazione [JUnit] (riga 6), garantisce l'integrazione con Spring tramite la classe [SpringJUnit4ClassRunner];
- riga 16: l'annotazione [SpringApplicationConfiguration] è un'annotazione [Spring] (riga 8) che consente di specificare le classi di configurazione per il test JUnit. Qui specifichiamo la classe [AppConfig] utilizzata per configurare il progetto. Abbiamo quindi accesso a tutti gli oggetti Spring definiti da questa classe di configurazione. In questo modo possiamo iniettare, alle righe 21–22, un riferimento al livello [DAO] che verrà testato;
- riga 25: L'annotazione [Before] indica un metodo che deve essere eseguito prima di ogni test;
- Righe 26–41: il metodo [init] imposta a zero i voti e i seggi nella tabella [LISTES] e il valore booleano [elimine] su [false];
L'unico metodo di test è il seguente:
@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]);
}
}
- righe 5-9: ci assicuriamo di poter recuperare il contenuto della tabella [CONF];
- righe 11–19: visualizziamo il contenuto della tabella [LISTES]. Qui non ci sono test, solo un controllo visivo;
- righe 21–35: modifichiamo la tabella [LISTES] nel database assegnando valori ai campi delle liste [voci, seggi, eliminati];
- righe 37–51: leggiamo nuovamente la tabella [LISTES] e verifichiamo che il risultato corrisponda a quanto inserito;
- righe 52–55: verifica visiva;
I risultati della console ottenuti con un livello [DAO] implementato sono i seguenti:
- righe 1-23: registri dei test primaverili;
- righe 25-26: il contenuto della tabella [CONF];
- righe 27-34: il contenuto iniziale della tabella [LISTES];
- righe 35-42: il contenuto della tabella [LISTES] dopo l'assegnazione dei valori ai campi [voix, sieges, elimine];
Inoltre, il test JUnit viene superato:
![]() |
7.11. Creazione dell'archivio [with-dependencies] per il livello [dao]
Il progetto finale presenta la seguente architettura:
![]() |
Esaminiamo la configurazione Maven del progetto:
<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>
![]() |
Genereremo un unico file JAR che conterrà le classi di tutti i JAR sopra indicati, oltre a quelle del progetto del livello [DAO]. Per farlo, è necessario apportare una modifica al file [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>
- righe 25–37: configurazione di un plugin Maven per generare il JAR;
- riga 15: specifica la versione di Java da utilizzare per la compilazione;
Una volta apportata questa modifica, è possibile generare il JAR come segue [1-10]:
![]() |
![]() |
- In [5], selezionare la cartella del progetto utilizzando il pulsante [6];
- in [7], assegnare un nome alla configurazione di build;
- in [8], inserisci l'elenco delle attività Maven da eseguire:
- [clean]: cancella la cartella [target] del progetto in cui verrà inserito il JAR generato;
- [compile]: compila il progetto;
- [assembly:single]: genera un singolo file JAR contenente tutte le classi del progetto e le relative dipendenze;
![]() |
- nei punti [9-10], verifica di disporre di un JDK (Java Development Kit) e non di un JRE (Java Runtime Environment). La differenza è che il JDK include un compilatore, mentre il JRE no. Se non disponi di un JDK, devi aggiungerne uno al punto [10] utilizzando [11]. Per farlo, segui la procedura descritta nella sezione 3.1;
![]() |
- In [17], esegua la configurazione di compilazione;
- in [13], l'archivio generato;
È possibile aprire questo archivio con un programma di decompressione:
![]() |
7.12. Test dell'archivio del livello [DAO]
Creiamo un progetto Eclipse standard (non Maven) [1]:
![]() |
Copiamo e incolliamo il pacchetto [dao.console] dal progetto [elections-dao-jdbc-01] nel progetto [elections-dao-jdbc-02] [2]. Vengono visualizzati diversi errori perché la classe [Main] fa riferimento a classi che non si trovano nel suo [Classpath]. Modificheremo il [Classpath].
Per prima cosa, creiamo [2-8] una cartella [lib] nel nuovo progetto:
![]() |
![]() |
In [9], inseriamo l'archivio creato nel passaggio precedente nella cartella [lib], quindi modifichiamo il percorso di compilazione del progetto:
![]() |
![]() |
![]() |
- in [18], abbiamo importato l'archivio del livello [DAO] creato in precedenza;
![]() |
- in [19], il progetto non mostra più alcun errore;
Possiamo quindi eseguire la classe [Main]. Otteniamo gli stessi risultati di prima.







































